Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/third_party/highway/hwy/ops/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 250 kB image not shown  

Quelle  generic_ops-inl.h

  Sprache: C
 

// Copyright 2021 Google LLC
// Copyright 2023 Arm Limited and/or its affiliates <open-source-office@arm.com>
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: BSD-3-Clause
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Target-independent types/functions defined after target-specific ops.

// The "include guards" in this file that check HWY_TARGET_TOGGLE serve to skip
// the generic implementation here if native ops are already defined.

#include "hwy/base.h"

// Define detail::Shuffle1230 etc, but only when viewing the current header;
// normally this is included via highway.h, which includes ops/*.h.
#if HWY_IDE && !defined(HWY_HIGHWAY_INCLUDED)
#include "hwy/detect_targets.h"
#include "hwy/ops/emu128-inl.h"
#endif  // HWY_IDE

// Relies on the external include guard in highway.h.
HWY_BEFORE_NAMESPACE();
namespace hwy {
namespace HWY_NAMESPACE {

// The lane type of a vector type, e.g. float for Vec<ScalableTag<float>>.
template <class V>
using LaneType = decltype(GetLane(V()));

// Vector type, e.g. Vec128<float> for CappedTag<float, 4>. Useful as the return
// type of functions that do not take a vector argument, or as an argument type
// if the function only has a template argument for D, or for explicit type
// names instead of auto. This may be a built-in type.
template <class D>
using Vec = decltype(Zero(D()));

// Mask type. Useful as the return type of functions that do not take a mask
// argument, or as an argument type if the function only has a template argument
// for D, or for explicit type names instead of auto.
template <class D>
using Mask = decltype(MaskFromVec(Zero(D())));

// Returns the closest value to v within [lo, hi].
template <class V>
HWY_API V Clamp(const V v, const V lo, const V hi) {
  return Min(Max(lo, v), hi);
}

// CombineShiftRightBytes (and -Lanes) are not available for the scalar target,
// and RVV has its own implementation of -Lanes.
#if HWY_TARGET != HWY_SCALAR && HWY_TARGET != HWY_RVV

template <size_t kLanes, class D>
HWY_API VFromD<D> CombineShiftRightLanes(D d, VFromD<D> hi, VFromD<D> lo) {
  constexpr size_t kBytes = kLanes * sizeof(TFromD<D>);
  static_assert(kBytes < 16"Shift count is per-block");
  return CombineShiftRightBytes<kBytes>(d, hi, lo);
}

#endif

// Returns lanes with the most significant bit set and all other bits zero.
template <class D>
HWY_API Vec<D> SignBit(D d) {
  const RebindToUnsigned<decltype(d)> du;
  return BitCast(d, Set(du, SignMask<TFromD<D>>()));
}

// Returns quiet NaN.
template <class D>
HWY_API Vec<D> NaN(D d) {
  const RebindToSigned<D> di;
  // LimitsMax sets all exponent and mantissa bits to 1. The exponent plus
  // mantissa MSB (to indicate quiet) would be sufficient.
  return BitCast(d, Set(di, LimitsMax<TFromD<decltype(di)>>()));
}

// Returns positive infinity.
template <class D>
HWY_API Vec<D> Inf(D d) {
  const RebindToUnsigned<D> du;
  using T = TFromD<D>;
  using TU = TFromD<decltype(du)>;
  const TU max_x2 = static_cast<TU>(MaxExponentTimes2<T>());
  return BitCast(d, Set(du, max_x2 >> 1));
}

// ------------------------------ ZeroExtendResizeBitCast

// The implementation of detail::ZeroExtendResizeBitCast for the HWY_EMU128
// target is in emu128-inl.h, and the implementation of
// detail::ZeroExtendResizeBitCast for the HWY_SCALAR target is in scalar-inl.h
#if HWY_TARGET != HWY_EMU128 && HWY_TARGET != HWY_SCALAR
namespace detail {

#if HWY_HAVE_SCALABLE
template <size_t kFromVectSize, size_t kToVectSize, class DTo, class DFrom>
HWY_INLINE VFromD<DTo> ZeroExtendResizeBitCast(
    hwy::SizeTag<kFromVectSize> /* from_size_tag */,
    hwy::SizeTag<kToVectSize> /* to_size_tag */, DTo d_to, DFrom d_from,
    VFromD<DFrom> v) {
  const Repartition<uint8_t, DTo> d_to_u8;
  const auto resized = ResizeBitCast(d_to_u8, v);
  // Zero the upper bytes which were not present/valid in d_from.
  const size_t num_bytes = Lanes(Repartition<uint8_t, decltype(d_from)>());
  return BitCast(d_to, IfThenElseZero(FirstN(d_to_u8, num_bytes), resized));
}
#else   // target that uses fixed-size vectors
// Truncating or same-size resizing cast: same as ResizeBitCast
template <size_t kFromVectSize, size_t kToVectSize, class DTo, class DFrom,
          HWY_IF_LANES_LE(kToVectSize, kFromVectSize)>
HWY_INLINE VFromD<DTo> ZeroExtendResizeBitCast(
    hwy::SizeTag<kFromVectSize> /* from_size_tag */,
    hwy::SizeTag<kToVectSize> /* to_size_tag */, DTo d_to, DFrom /*d_from*/,
    VFromD<DFrom> v) {
  return ResizeBitCast(d_to, v);
}

// Resizing cast to vector that has twice the number of lanes of the source
// vector
template <size_t kFromVectSize, size_t kToVectSize, class DTo, class DFrom,
          HWY_IF_LANES(kToVectSize, kFromVectSize * 2)>
HWY_INLINE VFromD<DTo> ZeroExtendResizeBitCast(
    hwy::SizeTag<kFromVectSize> /* from_size_tag */,
    hwy::SizeTag<kToVectSize> /* to_size_tag */, DTo d_to, DFrom d_from,
    VFromD<DFrom> v) {
  const Twice<decltype(d_from)> dt_from;
  return BitCast(d_to, ZeroExtendVector(dt_from, v));
}

// Resizing cast to vector that has more than twice the number of lanes of the
// source vector
template <size_t kFromVectSize, size_t kToVectSize, class DTo, class DFrom,
          HWY_IF_LANES_GT(kToVectSize, kFromVectSize * 2)>
HWY_INLINE VFromD<DTo> ZeroExtendResizeBitCast(
    hwy::SizeTag<kFromVectSize> /* from_size_tag */,
    hwy::SizeTag<kToVectSize> /* to_size_tag */, DTo d_to, DFrom /*d_from*/,
    VFromD<DFrom> v) {
  using TFrom = TFromD<DFrom>;
  constexpr size_t kNumOfFromLanes = kFromVectSize / sizeof(TFrom);
  const Repartition<TFrom, decltype(d_to)> d_resize_to;
  return BitCast(d_to, IfThenElseZero(FirstN(d_resize_to, kNumOfFromLanes),
                                      ResizeBitCast(d_resize_to, v)));
}
#endif  // HWY_HAVE_SCALABLE

}  // namespace detail
#endif  // HWY_TARGET != HWY_EMU128 && HWY_TARGET != HWY_SCALAR

template <class DTo, class DFrom>
HWY_API VFromD<DTo> ZeroExtendResizeBitCast(DTo d_to, DFrom d_from,
                                            VFromD<DFrom> v) {
  return detail::ZeroExtendResizeBitCast(hwy::SizeTag<d_from.MaxBytes()>(),
                                         hwy::SizeTag<d_to.MaxBytes()>(), d_to,
                                         d_from, v);
}

// ------------------------------ SafeFillN

template <class D, typename T = TFromD<D>>
HWY_API void SafeFillN(const size_t num, const T value, D d,
                       T* HWY_RESTRICT to) {
#if HWY_MEM_OPS_MIGHT_FAULT
  (void)d;
  for (size_t i = 0; i < num; ++i) {
    to[i] = value;
  }
#else
  BlendedStore(Set(d, value), FirstN(d, num), d, to);
#endif
}

// ------------------------------ SafeCopyN

template <class D, typename T = TFromD<D>>
HWY_API void SafeCopyN(const size_t num, D d, const T* HWY_RESTRICT from,
                       T* HWY_RESTRICT to) {
#if HWY_MEM_OPS_MIGHT_FAULT
  (void)d;
  for (size_t i = 0; i < num; ++i) {
    to[i] = from[i];
  }
#else
  const Mask<D> mask = FirstN(d, num);
  BlendedStore(MaskedLoad(mask, d, from), mask, d, to);
#endif
}

// ------------------------------ MaskFalse
#if (defined(HWY_NATIVE_MASK_FALSE) == defined(HWY_TARGET_TOGGLE))
#ifdef HWY_NATIVE_MASK_FALSE
#undef HWY_NATIVE_MASK_FALSE
#else
#define HWY_NATIVE_MASK_FALSE
#endif

template <class D>
HWY_API Mask<D> MaskFalse(D d) {
  return MaskFromVec(Zero(d));
}

#endif  // HWY_NATIVE_MASK_FALSE

// ------------------------------ BitwiseIfThenElse
#if (defined(HWY_NATIVE_BITWISE_IF_THEN_ELSE) == defined(HWY_TARGET_TOGGLE))
#ifdef HWY_NATIVE_BITWISE_IF_THEN_ELSE
#undef HWY_NATIVE_BITWISE_IF_THEN_ELSE
#else
#define HWY_NATIVE_BITWISE_IF_THEN_ELSE
#endif

template <class V>
HWY_API V BitwiseIfThenElse(V mask, V yes, V no) {
  return Or(And(mask, yes), AndNot(mask, no));
}

#endif  // HWY_NATIVE_BITWISE_IF_THEN_ELSE

// ------------------------------ PromoteMaskTo

#if (defined(HWY_NATIVE_PROMOTE_MASK_TO) == defined(HWY_TARGET_TOGGLE))
#ifdef HWY_NATIVE_PROMOTE_MASK_TO
#undef HWY_NATIVE_PROMOTE_MASK_TO
#else
#define HWY_NATIVE_PROMOTE_MASK_TO
#endif

template <class DTo, class DFrom>
HWY_API Mask<DTo> PromoteMaskTo(DTo d_to, DFrom d_from, Mask<DFrom> m) {
  static_assert(
      sizeof(TFromD<DTo>) > sizeof(TFromD<DFrom>),
      "sizeof(TFromD<DTo>) must be greater than sizeof(TFromD<DFrom>)");
  static_assert(
      IsSame<Mask<DFrom>, Mask<Rebind<TFromD<DFrom>, DTo>>>(),
      "Mask<DFrom> must be the same type as Mask<Rebind<TFromD<DFrom>, DTo>>");

  const RebindToSigned<decltype(d_to)> di_to;
  const RebindToSigned<decltype(d_from)> di_from;

  return MaskFromVec(BitCast(
      d_to, PromoteTo(di_to, BitCast(di_from, VecFromMask(d_from, m)))));
}

#endif  // HWY_NATIVE_PROMOTE_MASK_TO

// ------------------------------ DemoteMaskTo

#if (defined(HWY_NATIVE_DEMOTE_MASK_TO) == defined(HWY_TARGET_TOGGLE))
#ifdef HWY_NATIVE_DEMOTE_MASK_TO
#undef HWY_NATIVE_DEMOTE_MASK_TO
#else
#define HWY_NATIVE_DEMOTE_MASK_TO
#endif

template <class DTo, class DFrom>
HWY_API Mask<DTo> DemoteMaskTo(DTo d_to, DFrom d_from, Mask<DFrom> m) {
  static_assert(sizeof(TFromD<DTo>) < sizeof(TFromD<DFrom>),
                "sizeof(TFromD<DTo>) must be less than sizeof(TFromD<DFrom>)");
  static_assert(
      IsSame<Mask<DFrom>, Mask<Rebind<TFromD<DFrom>, DTo>>>(),
      "Mask<DFrom> must be the same type as Mask<Rebind<TFromD<DFrom>, DTo>>");

  const RebindToSigned<decltype(d_to)> di_to;
  const RebindToSigned<decltype(d_from)> di_from;

  return MaskFromVec(
      BitCast(d_to, DemoteTo(di_to, BitCast(di_from, VecFromMask(d_from, m)))));
}

#endif  // HWY_NATIVE_DEMOTE_MASK_TO

// ------------------------------ CombineMasks

#if (defined(HWY_NATIVE_COMBINE_MASKS) == defined(HWY_TARGET_TOGGLE))
#ifdef HWY_NATIVE_COMBINE_MASKS
#undef HWY_NATIVE_COMBINE_MASKS
#else
#define HWY_NATIVE_COMBINE_MASKS
#endif

#if HWY_TARGET != HWY_SCALAR
template <class D>
HWY_API Mask<D> CombineMasks(D d, Mask<Half<D>> hi, Mask<Half<D>> lo) {
  const Half<decltype(d)> dh;
  return MaskFromVec(Combine(d, VecFromMask(dh, hi), VecFromMask(dh, lo)));
}
#endif

#endif  // HWY_NATIVE_COMBINE_MASKS

// ------------------------------ LowerHalfOfMask

#if (defined(HWY_NATIVE_LOWER_HALF_OF_MASK) == defined(HWY_TARGET_TOGGLE))
#ifdef HWY_NATIVE_LOWER_HALF_OF_MASK
#undef HWY_NATIVE_LOWER_HALF_OF_MASK
#else
#define HWY_NATIVE_LOWER_HALF_OF_MASK
#endif

template <class D>
HWY_API Mask<D> LowerHalfOfMask(D d, Mask<Twice<D>> m) {
  const Twice<decltype(d)> dt;
  return MaskFromVec(LowerHalf(d, VecFromMask(dt, m)));
}

#endif  // HWY_NATIVE_LOWER_HALF_OF_MASK

// ------------------------------ UpperHalfOfMask

#if (defined(HWY_NATIVE_UPPER_HALF_OF_MASK) == defined(HWY_TARGET_TOGGLE))
#ifdef HWY_NATIVE_UPPER_HALF_OF_MASK
#undef HWY_NATIVE_UPPER_HALF_OF_MASK
#else
#define HWY_NATIVE_UPPER_HALF_OF_MASK
#endif

#if HWY_TARGET != HWY_SCALAR
template <class D>
HWY_API Mask<D> UpperHalfOfMask(D d, Mask<Twice<D>> m) {
  const Twice<decltype(d)> dt;
  return MaskFromVec(UpperHalf(d, VecFromMask(dt, m)));
}
#endif

#endif  // HWY_NATIVE_UPPER_HALF_OF_MASK

// ------------------------------ OrderedDemote2MasksTo

#if (defined(HWY_NATIVE_ORDERED_DEMOTE_2_MASKS_TO) == \
     defined(HWY_TARGET_TOGGLE))
#ifdef HWY_NATIVE_ORDERED_DEMOTE_2_MASKS_TO
#undef HWY_NATIVE_ORDERED_DEMOTE_2_MASKS_TO
#else
#define HWY_NATIVE_ORDERED_DEMOTE_2_MASKS_TO
#endif

#if HWY_TARGET != HWY_SCALAR
template <class DTo, class DFrom>
HWY_API Mask<DTo> OrderedDemote2MasksTo(DTo d_to, DFrom d_from, Mask<DFrom> a,
                                        Mask<DFrom> b) {
  static_assert(
      sizeof(TFromD<DTo>) == sizeof(TFromD<DFrom>) / 2,
      "sizeof(TFromD<DTo>) must be equal to sizeof(TFromD<DFrom>) / 2");
  static_assert(IsSame<Mask<DTo>, Mask<Repartition<TFromD<DTo>, DFrom>>>(),
                "Mask<DTo> must be the same type as "
                "Mask<Repartition<TFromD<DTo>, DFrom>>>()");

  const RebindToSigned<decltype(d_from)> di_from;
  const RebindToSigned<decltype(d_to)> di_to;

  const auto va = BitCast(di_from, VecFromMask(d_from, a));
  const auto vb = BitCast(di_from, VecFromMask(d_from, b));
  return MaskFromVec(BitCast(d_to, OrderedDemote2To(di_to, va, vb)));
}
#endif

#endif  // HWY_NATIVE_ORDERED_DEMOTE_2_MASKS_TO

// ------------------------------ InterleaveWholeLower/InterleaveWholeUpper
#if (defined(HWY_NATIVE_INTERLEAVE_WHOLE) == defined(HWY_TARGET_TOGGLE))
#ifdef HWY_NATIVE_INTERLEAVE_WHOLE
#undef HWY_NATIVE_INTERLEAVE_WHOLE
#else
#define HWY_NATIVE_INTERLEAVE_WHOLE
#endif

#if HWY_TARGET != HWY_SCALAR
template <class D, HWY_IF_V_SIZE_LE_D(D, 16)>
HWY_API VFromD<D> InterleaveWholeLower(D d, VFromD<D> a, VFromD<D> b) {
  // InterleaveWholeLower(d, a, b) is equivalent to InterleaveLower(a, b) if
  // D().MaxBytes() <= 16 is true
  return InterleaveLower(d, a, b);
}
template <class D, HWY_IF_V_SIZE_LE_D(D, 16)>
HWY_API VFromD<D> InterleaveWholeUpper(D d, VFromD<D> a, VFromD<D> b) {
  // InterleaveWholeUpper(d, a, b) is equivalent to InterleaveUpper(a, b) if
  // D().MaxBytes() <= 16 is true
  return InterleaveUpper(d, a, b);
}

// InterleaveWholeLower/InterleaveWholeUpper for 32-byte vectors on AVX2/AVX3
// is implemented in x86_256-inl.h.

// InterleaveWholeLower/InterleaveWholeUpper for 64-byte vectors on AVX3 is
// implemented in x86_512-inl.h.

// InterleaveWholeLower/InterleaveWholeUpper for 32-byte vectors on WASM_EMU256
// is implemented in wasm_256-inl.h.
#endif  // HWY_TARGET != HWY_SCALAR

#endif  // HWY_NATIVE_INTERLEAVE_WHOLE

#if HWY_TARGET != HWY_SCALAR
// The InterleaveWholeLower without the optional D parameter is generic for all
// vector lengths.
template <class V>
HWY_API V InterleaveWholeLower(V a, V b) {
  return InterleaveWholeLower(DFromV<V>(), a, b);
}
#endif  // HWY_TARGET != HWY_SCALAR

// ------------------------------ AddSub

template <class V, HWY_IF_LANES_D(DFromV<V>, 1)>
HWY_API V AddSub(V a, V b) {
  // AddSub(a, b) for a one-lane vector is equivalent to Sub(a, b)
  return Sub(a, b);
}

// AddSub for F32x2, F32x4, and F64x2 vectors is implemented in x86_128-inl.h on
// SSSE3/SSE4/AVX2/AVX3

// AddSub for F32x8 and F64x4 vectors is implemented in x86_256-inl.h on
// AVX2/AVX3
template <class V, HWY_IF_V_SIZE_GT_V(V, ((HWY_TARGET <= HWY_SSSE3 &&
                                           hwy::IsFloat3264<TFromV<V>>())
                                              ? 32
                                              : sizeof(TFromV<V>)))>
HWY_API V AddSub(V a, V b) {
  using D = DFromV<decltype(a)>;
  using T = TFromD<D>;
  using TNegate = If<!hwy::IsSigned<T>(), MakeSigned<T>, T>;

  const D d;
  const Rebind<TNegate, D> d_negate;

  // Negate the even lanes of b
  const auto negated_even_b = OddEven(b, BitCast(d, Neg(BitCast(d_negate, b))));

  return Add(a, negated_even_b);
}

// ------------------------------ MaskedAddOr etc.
#if (defined(HWY_NATIVE_MASKED_ARITH) == defined(HWY_TARGET_TOGGLE))
#ifdef HWY_NATIVE_MASKED_ARITH
#undef HWY_NATIVE_MASKED_ARITH
#else
#define HWY_NATIVE_MASKED_ARITH
#endif

template <class V, class M>
HWY_API V MaskedMinOr(V no, M m, V a, V b) {
  return IfThenElse(m, Min(a, b), no);
}

template <class V, class M>
HWY_API V MaskedMaxOr(V no, M m, V a, V b) {
  return IfThenElse(m, Max(a, b), no);
}

template <class V, class M>
HWY_API V MaskedAddOr(V no, M m, V a, V b) {
  return IfThenElse(m, Add(a, b), no);
}

template <class V, class M>
HWY_API V MaskedSubOr(V no, M m, V a, V b) {
  return IfThenElse(m, Sub(a, b), no);
}

template <class V, class M>
HWY_API V MaskedMulOr(V no, M m, V a, V b) {
  return IfThenElse(m, Mul(a, b), no);
}

template <class V, class M>
HWY_API V MaskedDivOr(V no, M m, V a, V b) {
  return IfThenElse(m, Div(a, b), no);
}

template <class V, class M>
HWY_API V MaskedModOr(V no, M m, V a, V b) {
  return IfThenElse(m, Mod(a, b), no);
}

template <class V, class M>
HWY_API V MaskedSatAddOr(V no, M m, V a, V b) {
  return IfThenElse(m, SaturatedAdd(a, b), no);
}

template <class V, class M>
HWY_API V MaskedSatSubOr(V no, M m, V a, V b) {
  return IfThenElse(m, SaturatedSub(a, b), no);
}
#endif  // HWY_NATIVE_MASKED_ARITH

// ------------------------------ IfNegativeThenNegOrUndefIfZero

#if (defined(HWY_NATIVE_INTEGER_IF_NEGATIVE_THEN_NEG) == \
     defined(HWY_TARGET_TOGGLE))
#ifdef HWY_NATIVE_INTEGER_IF_NEGATIVE_THEN_NEG
#undef HWY_NATIVE_INTEGER_IF_NEGATIVE_THEN_NEG
#else
#define HWY_NATIVE_INTEGER_IF_NEGATIVE_THEN_NEG
#endif

template <class V, HWY_IF_NOT_FLOAT_NOR_SPECIAL_V(V)>
HWY_API V IfNegativeThenNegOrUndefIfZero(V mask, V v) {
#if HWY_HAVE_SCALABLE || HWY_TARGET == HWY_SVE_256 || HWY_TARGET == HWY_SVE2_128
  // MaskedSubOr is more efficient than IfNegativeThenElse on RVV/SVE
  const auto zero = Zero(DFromV<V>());
  return MaskedSubOr(v, Lt(mask, zero), zero, v);
#else
  return IfNegativeThenElse(mask, Neg(v), v);
#endif
}

#endif  // HWY_NATIVE_INTEGER_IF_NEGATIVE_THEN_NEG

template <class V, HWY_IF_FLOAT_V(V)>
HWY_API V IfNegativeThenNegOrUndefIfZero(V mask, V v) {
  return CopySign(v, Xor(mask, v));
}

// ------------------------------ SaturatedNeg

#if (defined(HWY_NATIVE_SATURATED_NEG_8_16_32) == defined(HWY_TARGET_TOGGLE))
#ifdef HWY_NATIVE_SATURATED_NEG_8_16_32
#undef HWY_NATIVE_SATURATED_NEG_8_16_32
#else
#define HWY_NATIVE_SATURATED_NEG_8_16_32
#endif

template <class V, HWY_IF_T_SIZE_ONE_OF_V(V, (1 << 1) | (1 << 2)),
          HWY_IF_SIGNED_V(V)>
HWY_API V SaturatedNeg(V v) {
  const DFromV<decltype(v)> d;
  return SaturatedSub(Zero(d), v);
}

template <class V, HWY_IF_I32(TFromV<V>)>
HWY_API V SaturatedNeg(V v) {
  const DFromV<decltype(v)> d;

#if HWY_TARGET == HWY_RVV ||                               \
    (HWY_TARGET >= HWY_PPC10 && HWY_TARGET <= HWY_PPC8) || \
    (HWY_TARGET >= HWY_SVE2_128 && HWY_TARGET <= HWY_NEON_WITHOUT_AES)
  // RVV/NEON/SVE/PPC have native I32 SaturatedSub instructions
  return SaturatedSub(Zero(d), v);
#else
  // ~v[i] - ((v[i] > LimitsMin<int32_t>()) ? -1 : 0) is equivalent to
  // (v[i] > LimitsMin<int32_t>) ? (-v[i]) : LimitsMax<int32_t>() since
  // -v[i] == ~v[i] + 1 == ~v[i] - (-1) and
  // ~LimitsMin<int32_t>() == LimitsMax<int32_t>().
  return Sub(Not(v), VecFromMask(d, Gt(v, Set(d, LimitsMin<int32_t>()))));
#endif
}
#endif  // HWY_NATIVE_SATURATED_NEG_8_16_32

#if (defined(HWY_NATIVE_SATURATED_NEG_64) == defined(HWY_TARGET_TOGGLE))
#ifdef HWY_NATIVE_SATURATED_NEG_64
#undef HWY_NATIVE_SATURATED_NEG_64
#else
#define HWY_NATIVE_SATURATED_NEG_64
#endif

template <class V, HWY_IF_I64(TFromV<V>)>
HWY_API V SaturatedNeg(V v) {
#if HWY_TARGET == HWY_RVV || \
    (HWY_TARGET >= HWY_SVE2_128 && HWY_TARGET <= HWY_NEON_WITHOUT_AES)
  // RVV/NEON/SVE have native I64 SaturatedSub instructions
  const DFromV<decltype(v)> d;
  return SaturatedSub(Zero(d), v);
#else
  const auto neg_v = Neg(v);
  return Add(neg_v, BroadcastSignBit(And(v, neg_v)));
#endif
}
#endif  // HWY_NATIVE_SATURATED_NEG_64

// ------------------------------ SaturatedAbs

#if (defined(HWY_NATIVE_SATURATED_ABS) == defined(HWY_TARGET_TOGGLE))
#ifdef HWY_NATIVE_SATURATED_ABS
#undef HWY_NATIVE_SATURATED_ABS
#else
#define HWY_NATIVE_SATURATED_ABS
#endif

template <class V, HWY_IF_SIGNED_V(V)>
HWY_API V SaturatedAbs(V v) {
  return Max(v, SaturatedNeg(v));
}

#endif

// ------------------------------ Reductions

// Targets follow one of two strategies. If HWY_NATIVE_REDUCE_SCALAR is toggled,
// they (RVV/SVE/Armv8/Emu128) implement ReduceSum and SumOfLanes via Set.
// Otherwise, they (Armv7/PPC/scalar/WASM/x86) define zero to most of the
// SumOfLanes overloads. For the latter group, we here define the remaining
// overloads, plus ReduceSum which uses them plus GetLane.
#if (defined(HWY_NATIVE_REDUCE_SCALAR) == defined(HWY_TARGET_TOGGLE))
#ifdef HWY_NATIVE_REDUCE_SCALAR
#undef HWY_NATIVE_REDUCE_SCALAR
#else
#define HWY_NATIVE_REDUCE_SCALAR
#endif

namespace detail {

// Allows reusing the same shuffle code for SumOfLanes/MinOfLanes/MaxOfLanes.
struct AddFunc {
  template <class V>
  V operator()(V a, V b) const {
    return Add(a, b);
  }
};

struct MinFunc {
  template <class V>
  V operator()(V a, V b) const {
    return Min(a, b);
  }
};

struct MaxFunc {
  template <class V>
  V operator()(V a, V b) const {
    return Max(a, b);
  }
};

// No-op for vectors of at most one block.
template <class D, class Func, HWY_IF_V_SIZE_LE_D(D, 16)>
HWY_INLINE VFromD<D> ReduceAcrossBlocks(D, Func, VFromD<D> v) {
  return v;
}

// Reduces a lane with its counterpart in other block(s). Shared by AVX2 and
// WASM_EMU256. AVX3 has its own overload.
template <class D, class Func, HWY_IF_V_SIZE_D(D, 32)>
HWY_INLINE VFromD<D> ReduceAcrossBlocks(D /*d*/, Func f, VFromD<D> v) {
  return f(v, SwapAdjacentBlocks(v));
}

// These return the reduction result broadcasted across all lanes. They assume
// the caller has already reduced across blocks.

template <class D, class Func, HWY_IF_LANES_PER_BLOCK_D(D, 2)>
HWY_INLINE VFromD<D> ReduceWithinBlocks(D d, Func f, VFromD<D> v10) {
  return f(v10, Reverse2(d, v10));
}

template <class D, class Func, HWY_IF_LANES_PER_BLOCK_D(D, 4)>
HWY_INLINE VFromD<D> ReduceWithinBlocks(D d, Func f, VFromD<D> v3210) {
  const VFromD<D> v0123 = Reverse4(d, v3210);
  const VFromD<D> v03_12_12_03 = f(v3210, v0123);
  const VFromD<D> v12_03_03_12 = Reverse2(d, v03_12_12_03);
  return f(v03_12_12_03, v12_03_03_12);
}

template <class D, class Func, HWY_IF_LANES_PER_BLOCK_D(D, 8)>
HWY_INLINE VFromD<D> ReduceWithinBlocks(D d, Func f, VFromD<D> v76543210) {
  // The upper half is reversed from the lower half; omit for brevity.
  const VFromD<D> v34_25_16_07 = f(v76543210, Reverse8(d, v76543210));
  const VFromD<D> v0347_1625_1625_0347 =
      f(v34_25_16_07, Reverse4(d, v34_25_16_07));
  return f(v0347_1625_1625_0347, Reverse2(d, v0347_1625_1625_0347));
}

template <class D, class Func, HWY_IF_LANES_PER_BLOCK_D(D, 16), HWY_IF_U8_D(D)>
HWY_INLINE VFromD<D> ReduceWithinBlocks(D d, Func f, VFromD<D> v) {
  const RepartitionToWide<decltype(d)> dw;
  using VW = VFromD<decltype(dw)>;
  const VW vw = BitCast(dw, v);
  // f is commutative, so no need to adapt for HWY_IS_LITTLE_ENDIAN.
  const VW even = And(vw, Set(dw, 0xFF));
  const VW odd = ShiftRight<8>(vw);
  const VW reduced = ReduceWithinBlocks(dw, f, f(even, odd));
#if HWY_IS_LITTLE_ENDIAN
  return DupEven(BitCast(d, reduced));
#else
  return DupOdd(BitCast(d, reduced));
#endif
}

template <class D, class Func, HWY_IF_LANES_PER_BLOCK_D(D, 16), HWY_IF_I8_D(D)>
HWY_INLINE VFromD<D> ReduceWithinBlocks(D d, Func f, VFromD<D> v) {
  const RepartitionToWide<decltype(d)> dw;
  using VW = VFromD<decltype(dw)>;
  const VW vw = BitCast(dw, v);
  // Sign-extend
  // f is commutative, so no need to adapt for HWY_IS_LITTLE_ENDIAN.
  const VW even = ShiftRight<8>(ShiftLeft<8>(vw));
  const VW odd = ShiftRight<8>(vw);
  const VW reduced = ReduceWithinBlocks(dw, f, f(even, odd));
#if HWY_IS_LITTLE_ENDIAN
  return DupEven(BitCast(d, reduced));
#else
  return DupOdd(BitCast(d, reduced));
#endif
}

}  // namespace detail

template <class D, HWY_IF_SUM_OF_LANES_D(D)>
HWY_API VFromD<D> SumOfLanes(D d, VFromD<D> v) {
  const detail::AddFunc f;
  v = detail::ReduceAcrossBlocks(d, f, v);
  return detail::ReduceWithinBlocks(d, f, v);
}
template <class D, HWY_IF_MINMAX_OF_LANES_D(D)>
HWY_API VFromD<D> MinOfLanes(D d, VFromD<D> v) {
  const detail::MinFunc f;
  v = detail::ReduceAcrossBlocks(d, f, v);
  return detail::ReduceWithinBlocks(d, f, v);
}
template <class D, HWY_IF_MINMAX_OF_LANES_D(D)>
HWY_API VFromD<D> MaxOfLanes(D d, VFromD<D> v) {
  const detail::MaxFunc f;
  v = detail::ReduceAcrossBlocks(d, f, v);
  return detail::ReduceWithinBlocks(d, f, v);
}

template <class D, HWY_IF_REDUCE_D(D)>
HWY_API TFromD<D> ReduceSum(D d, VFromD<D> v) {
  return GetLane(SumOfLanes(d, v));
}
template <class D, HWY_IF_REDUCE_D(D)>
HWY_API TFromD<D> ReduceMin(D d, VFromD<D> v) {
  return GetLane(MinOfLanes(d, v));
}
template <class D, HWY_IF_REDUCE_D(D)>
HWY_API TFromD<D> ReduceMax(D d, VFromD<D> v) {
  return GetLane(MaxOfLanes(d, v));
}

#endif  // HWY_NATIVE_REDUCE_SCALAR

// Corner cases for both generic and native implementations:
// N=1 (native covers N=2 e.g. for u64x2 and even u32x2 on Arm)
template <class D, HWY_IF_LANES_D(D, 1)>
HWY_API TFromD<D> ReduceSum(D /*d*/, VFromD<D> v) {
  return GetLane(v);
}
template <class D, HWY_IF_LANES_D(D, 1)>
HWY_API TFromD<D> ReduceMin(D /*d*/, VFromD<D> v) {
  return GetLane(v);
}
template <class D, HWY_IF_LANES_D(D, 1)>
HWY_API TFromD<D> ReduceMax(D /*d*/, VFromD<D> v) {
  return GetLane(v);
}

template <class D, HWY_IF_LANES_D(D, 1)>
HWY_API VFromD<D> SumOfLanes(D /* tag */, VFromD<D> v) {
  return v;
}
template <class D, HWY_IF_LANES_D(D, 1)>
HWY_API VFromD<D> MinOfLanes(D /* tag */, VFromD<D> v) {
  return v;
}
template <class D, HWY_IF_LANES_D(D, 1)>
HWY_API VFromD<D> MaxOfLanes(D /* tag */, VFromD<D> v) {
  return v;
}

// N=4 for 8-bit is still less than the minimum native size.

// ARMv7 NEON/PPC/RVV/SVE have target-specific implementations of the N=4 I8/U8
// ReduceSum operations
#if (defined(HWY_NATIVE_REDUCE_SUM_4_UI8) == defined(HWY_TARGET_TOGGLE))
#ifdef HWY_NATIVE_REDUCE_SUM_4_UI8
#undef HWY_NATIVE_REDUCE_SUM_4_UI8
#else
#define HWY_NATIVE_REDUCE_SUM_4_UI8
#endif
template <class D, HWY_IF_V_SIZE_D(D, 4), HWY_IF_UI8_D(D)>
HWY_API TFromD<D> ReduceSum(D d, VFromD<D> v) {
  const Twice<RepartitionToWide<decltype(d)>> dw;
  return static_cast<TFromD<D>>(ReduceSum(dw, PromoteTo(dw, v)));
}
#endif  // HWY_NATIVE_REDUCE_SUM_4_UI8

// RVV/SVE have target-specific implementations of the N=4 I8/U8
// ReduceMin/ReduceMax operations
#if (defined(HWY_NATIVE_REDUCE_MINMAX_4_UI8) == defined(HWY_TARGET_TOGGLE))
#ifdef HWY_NATIVE_REDUCE_MINMAX_4_UI8
#undef HWY_NATIVE_REDUCE_MINMAX_4_UI8
#else
#define HWY_NATIVE_REDUCE_MINMAX_4_UI8
#endif
template <class D, HWY_IF_V_SIZE_D(D, 4), HWY_IF_UI8_D(D)>
HWY_API TFromD<D> ReduceMin(D d, VFromD<D> v) {
  const Twice<RepartitionToWide<decltype(d)>> dw;
  return static_cast<TFromD<D>>(ReduceMin(dw, PromoteTo(dw, v)));
}
template <class D, HWY_IF_V_SIZE_D(D, 4), HWY_IF_UI8_D(D)>
HWY_API TFromD<D> ReduceMax(D d, VFromD<D> v) {
  const Twice<RepartitionToWide<decltype(d)>> dw;
  return static_cast<TFromD<D>>(ReduceMax(dw, PromoteTo(dw, v)));
}
#endif  // HWY_NATIVE_REDUCE_MINMAX_4_UI8

// ------------------------------ IsInf, IsFinite

// AVX3 has target-specific implementations of these.
#if (defined(HWY_NATIVE_ISINF) == defined(HWY_TARGET_TOGGLE))
#ifdef HWY_NATIVE_ISINF
#undef HWY_NATIVE_ISINF
#else
#define HWY_NATIVE_ISINF
#endif

template <class V, class D = DFromV<V>>
HWY_API MFromD<D> IsInf(const V v) {
  using T = TFromD<D>;
  const D d;
  const RebindToUnsigned<decltype(d)> du;
  const VFromD<decltype(du)> vu = BitCast(du, v);
  // 'Shift left' to clear the sign bit, check for exponent=max and mantissa=0.
  return RebindMask(
      d,
      Eq(Add(vu, vu),
         Set(du, static_cast<MakeUnsigned<T>>(hwy::MaxExponentTimes2<T>()))));
}

// Returns whether normal/subnormal/zero.
template <class V, class D = DFromV<V>>
HWY_API MFromD<D> IsFinite(const V v) {
  using T = TFromD<D>;
  const D d;
  const RebindToUnsigned<decltype(d)> du;
  const RebindToSigned<decltype(d)> di;  // cheaper than unsigned comparison
  const VFromD<decltype(du)> vu = BitCast(du, v);
// 'Shift left' to clear the sign bit. MSVC seems to generate incorrect code
// for AVX2 if we instead add vu + vu.
#if HWY_COMPILER_MSVC
  const VFromD<decltype(du)> shl = ShiftLeft<1>(vu);
#else
  const VFromD<decltype(du)> shl = Add(vu, vu);
#endif

  // Then shift right so we can compare with the max exponent (cannot compare
  // with MaxExponentTimes2 directly because it is negative and non-negative
  // floats would be greater).
  const VFromD<decltype(di)> exp =
      BitCast(di, ShiftRight<hwy::MantissaBits<T>() + 1>(shl));
  return RebindMask(d, Lt(exp, Set(di, hwy::MaxExponentField<T>())));
}

#endif  // HWY_NATIVE_ISINF

// ------------------------------ LoadInterleaved2

#if HWY_IDE || \
    (defined(HWY_NATIVE_LOAD_STORE_INTERLEAVED) == defined(HWY_TARGET_TOGGLE))
#ifdef HWY_NATIVE_LOAD_STORE_INTERLEAVED
#undef HWY_NATIVE_LOAD_STORE_INTERLEAVED
#else
#define HWY_NATIVE_LOAD_STORE_INTERLEAVED
#endif

template <class D, HWY_IF_LANES_GT_D(D, 1)>
HWY_API void LoadInterleaved2(D d, const TFromD<D>* HWY_RESTRICT unaligned,
                              VFromD<D>& v0, VFromD<D>& v1) {
  const VFromD<D> A = LoadU(d, unaligned);  // v1[1] v0[1] v1[0] v0[0]
  const VFromD<D> B = LoadU(d, unaligned + Lanes(d));
  v0 = ConcatEven(d, B, A);
  v1 = ConcatOdd(d, B, A);
}

template <class D, HWY_IF_LANES_D(D, 1)>
HWY_API void LoadInterleaved2(D d, const TFromD<D>* HWY_RESTRICT unaligned,
                              VFromD<D>& v0, VFromD<D>& v1) {
  v0 = LoadU(d, unaligned + 0);
  v1 = LoadU(d, unaligned + 1);
}

// ------------------------------ LoadInterleaved3 (CombineShiftRightBytes)

namespace detail {

#if HWY_IDE
template <class V>
HWY_INLINE V ShuffleTwo1230(V a, V /* b */) {
  return a;
}
template <class V>
HWY_INLINE V ShuffleTwo2301(V a, V /* b */) {
  return a;
}
template <class V>
HWY_INLINE V ShuffleTwo3012(V a, V /* b */) {
  return a;
}
#endif  // HWY_IDE

// Default for <= 128-bit vectors; x86_256 and x86_512 have their own overload.
template <class D, HWY_IF_V_SIZE_LE_D(D, 16)>
HWY_INLINE void LoadTransposedBlocks3(D d,
                                      const TFromD<D>* HWY_RESTRICT unaligned,
                                      VFromD<D>& A, VFromD<D>& B,
                                      VFromD<D>& C) {
  constexpr size_t kN = MaxLanes(d);
  A = LoadU(d, unaligned + 0 * kN);
  B = LoadU(d, unaligned + 1 * kN);
  C = LoadU(d, unaligned + 2 * kN);
}

}  // namespace detail

template <class D, HWY_IF_LANES_PER_BLOCK_D(D, 16)>
HWY_API void LoadInterleaved3(D d, const TFromD<D>* HWY_RESTRICT unaligned,
                              VFromD<D>& v0, VFromD<D>& v1, VFromD<D>& v2) {
  const RebindToUnsigned<decltype(d)> du;
  using V = VFromD<D>;
  using VU = VFromD<decltype(du)>;
  // Compact notation so these fit on one line: 12 := v1[2].
  V A;  // 05 24 14 04 23 13 03 22 12 02 21 11 01 20 10 00
  V B;  // 1a 0a 29 19 09 28 18 08 27 17 07 26 16 06 25 15
  V C;  // 2f 1f 0f 2e 1e 0e 2d 1d 0d 2c 1c 0c 2b 1b 0b 2a
  detail::LoadTransposedBlocks3(d, unaligned, A, B, C);
  // Compress all lanes belonging to v0 into consecutive lanes.
  constexpr uint8_t Z = 0x80;
  const VU idx_v0A =
      Dup128VecFromValues(du, 03691215, Z, Z, Z, Z, Z, Z, Z, Z, Z, Z);
  const VU idx_v0B =
      Dup128VecFromValues(du, Z, Z, Z, Z, Z, Z, 2581114, Z, Z, Z, Z, Z);
  const VU idx_v0C =
      Dup128VecFromValues(du, Z, Z, Z, Z, Z, Z, Z, Z, Z, Z, Z, 1471013);
  const VU idx_v1A =
      Dup128VecFromValues(du, 1471013, Z, Z, Z, Z, Z, Z, Z, Z, Z, Z, Z);
  const VU idx_v1B =
      Dup128VecFromValues(du, Z, Z, Z, Z, Z, 03691215, Z, Z, Z, Z, Z);
  const VU idx_v1C =
      Dup128VecFromValues(du, Z, Z, Z, Z, Z, Z, Z, Z, Z, Z, Z, 2581114);
  const VU idx_v2A =
      Dup128VecFromValues(du, 2581114, Z, Z, Z, Z, Z, Z, Z, Z, Z, Z, Z);
  const VU idx_v2B =
      Dup128VecFromValues(du, Z, Z, Z, Z, Z, 1471013, Z, Z, Z, Z, Z, Z);
  const VU idx_v2C =
      Dup128VecFromValues(du, Z, Z, Z, Z, Z, Z, Z, Z, Z, Z, 03691215);
  const V v0L = BitCast(d, TableLookupBytesOr0(A, idx_v0A));
  const V v0M = BitCast(d, TableLookupBytesOr0(B, idx_v0B));
  const V v0U = BitCast(d, TableLookupBytesOr0(C, idx_v0C));
  const V v1L = BitCast(d, TableLookupBytesOr0(A, idx_v1A));
  const V v1M = BitCast(d, TableLookupBytesOr0(B, idx_v1B));
  const V v1U = BitCast(d, TableLookupBytesOr0(C, idx_v1C));
  const V v2L = BitCast(d, TableLookupBytesOr0(A, idx_v2A));
  const V v2M = BitCast(d, TableLookupBytesOr0(B, idx_v2B));
  const V v2U = BitCast(d, TableLookupBytesOr0(C, idx_v2C));
  v0 = Xor3(v0L, v0M, v0U);
  v1 = Xor3(v1L, v1M, v1U);
  v2 = Xor3(v2L, v2M, v2U);
}

// 8-bit lanes x8
template <class D, HWY_IF_LANES_PER_BLOCK_D(D, 8), HWY_IF_T_SIZE_D(D, 1)>
HWY_API void LoadInterleaved3(D d, const TFromD<D>* HWY_RESTRICT unaligned,
                              VFromD<D>& v0, VFromD<D>& v1, VFromD<D>& v2) {
  const RebindToUnsigned<decltype(d)> du;
  using V = VFromD<D>;
  using VU = VFromD<decltype(du)>;
  V A;  // v1[2] v0[2] v2[1] v1[1] v0[1] v2[0] v1[0] v0[0]
  V B;  // v0[5] v2[4] v1[4] v0[4] v2[3] v1[3] v0[3] v2[2]
  V C;  // v2[7] v1[7] v0[7] v2[6] v1[6] v0[6] v2[5] v1[5]
  detail::LoadTransposedBlocks3(d, unaligned, A, B, C);
  // Compress all lanes belonging to v0 into consecutive lanes.
  constexpr uint8_t Z = 0x80;
  const VU idx_v0A =
      Dup128VecFromValues(du, 036, Z, Z, Z, Z, Z, 00000000);
  const VU idx_v0B =
      Dup128VecFromValues(du, Z, Z, Z, 147, Z, Z, 00000000);
  const VU idx_v0C =
      Dup128VecFromValues(du, Z, Z, Z, Z, Z, Z, 2500000000);
  const VU idx_v1A =
      Dup128VecFromValues(du, 147, Z, Z, Z, Z, Z, 00000000);
  const VU idx_v1B =
      Dup128VecFromValues(du, Z, Z, Z, 25, Z, Z, Z, 00000000);
  const VU idx_v1C =
      Dup128VecFromValues(du, Z, Z, Z, Z, Z, 03600000000);
  const VU idx_v2A =
      Dup128VecFromValues(du, 25, Z, Z, Z, Z, Z, Z, 00000000);
  const VU idx_v2B =
      Dup128VecFromValues(du, Z, Z, 036, Z, Z, Z, 00000000);
  const VU idx_v2C =
      Dup128VecFromValues(du, Z, Z, Z, Z, Z, 14700000000);
  const V v0L = BitCast(d, TableLookupBytesOr0(A, idx_v0A));
  const V v0M = BitCast(d, TableLookupBytesOr0(B, idx_v0B));
  const V v0U = BitCast(d, TableLookupBytesOr0(C, idx_v0C));
  const V v1L = BitCast(d, TableLookupBytesOr0(A, idx_v1A));
  const V v1M = BitCast(d, TableLookupBytesOr0(B, idx_v1B));
  const V v1U = BitCast(d, TableLookupBytesOr0(C, idx_v1C));
  const V v2L = BitCast(d, TableLookupBytesOr0(A, idx_v2A));
  const V v2M = BitCast(d, TableLookupBytesOr0(B, idx_v2B));
  const V v2U = BitCast(d, TableLookupBytesOr0(C, idx_v2C));
  v0 = Xor3(v0L, v0M, v0U);
  v1 = Xor3(v1L, v1M, v1U);
  v2 = Xor3(v2L, v2M, v2U);
}

// 16-bit lanes x8
template <class D, HWY_IF_LANES_PER_BLOCK_D(D, 8), HWY_IF_T_SIZE_D(D, 2)>
HWY_API void LoadInterleaved3(D d, const TFromD<D>* HWY_RESTRICT unaligned,
                              VFromD<D>& v0, VFromD<D>& v1, VFromD<D>& v2) {
  const RebindToUnsigned<decltype(d)> du;
  const Repartition<uint8_t, decltype(du)> du8;
  using V = VFromD<D>;
  using VU8 = VFromD<decltype(du8)>;
  V A;  // v1[2] v0[2] v2[1] v1[1] v0[1] v2[0] v1[0] v0[0]
  V B;  // v0[5] v2[4] v1[4] v0[4] v2[3] v1[3] v0[3] v2[2]
  V C;  // v2[7] v1[7] v0[7] v2[6] v1[6] v0[6] v2[5] v1[5]
  detail::LoadTransposedBlocks3(d, unaligned, A, B, C);
  // Compress all lanes belonging to v0 into consecutive lanes. Same as above,
  // but each element of the array contains a byte index for a byte of a lane.
  constexpr uint8_t Z = 0x80;
  const VU8 idx_v0A = Dup128VecFromValues(du8, 0x00, 0x01, 0x06, 0x07, 0x0C,
                                          0x0D, Z, Z, Z, Z, Z, Z, Z, Z, Z, Z);
  const VU8 idx_v0B = Dup128VecFromValues(du8, Z, Z, Z, Z, Z, Z, 0x02, 0x03,
                                          0x08, 0x09, 0x0E, 0x0F, Z, Z, Z, Z);
  const VU8 idx_v0C = Dup128VecFromValues(du8, Z, Z, Z, Z, Z, Z, Z, Z, Z, Z, Z,
                                          Z, 0x04, 0x05, 0x0A, 0x0B);
  const VU8 idx_v1A = Dup128VecFromValues(du8, 0x02, 0x03, 0x08, 0x09, 0x0E,
                                          0x0F, Z, Z, Z, Z, Z, Z, Z, Z, Z, Z);
  const VU8 idx_v1B = Dup128VecFromValues(du8, Z, Z, Z, Z, Z, Z, 0x04, 0x05,
                                          0x0A, 0x0B, Z, Z, Z, Z, Z, Z);
  const VU8 idx_v1C = Dup128VecFromValues(du8, Z, Z, Z, Z, Z, Z, Z, Z, Z, Z,
                                          0x00, 0x01, 0x06, 0x07, 0x0C, 0x0D);
  const VU8 idx_v2A = Dup128VecFromValues(du8, 0x04, 0x05, 0x0A, 0x0B, Z, Z, Z,
                                          Z, Z, Z, Z, Z, Z, Z, Z, Z);
  const VU8 idx_v2B = Dup128VecFromValues(du8, Z, Z, Z, Z, 0x00, 0x01, 0x06,
                                          0x07, 0x0C, 0x0D, Z, Z, Z, Z, Z, Z);
  const VU8 idx_v2C = Dup128VecFromValues(du8, Z, Z, Z, Z, Z, Z, Z, Z, Z, Z,
                                          0x02, 0x03, 0x08, 0x09, 0x0E, 0x0F);
  const V v0L = TableLookupBytesOr0(A, BitCast(d, idx_v0A));
  const V v0M = TableLookupBytesOr0(B, BitCast(d, idx_v0B));
  const V v0U = TableLookupBytesOr0(C, BitCast(d, idx_v0C));
  const V v1L = TableLookupBytesOr0(A, BitCast(d, idx_v1A));
  const V v1M = TableLookupBytesOr0(B, BitCast(d, idx_v1B));
  const V v1U = TableLookupBytesOr0(C, BitCast(d, idx_v1C));
  const V v2L = TableLookupBytesOr0(A, BitCast(d, idx_v2A));
  const V v2M = TableLookupBytesOr0(B, BitCast(d, idx_v2B));
  const V v2U = TableLookupBytesOr0(C, BitCast(d, idx_v2C));
  v0 = Xor3(v0L, v0M, v0U);
  v1 = Xor3(v1L, v1M, v1U);
  v2 = Xor3(v2L, v2M, v2U);
}

template <class D, HWY_IF_LANES_PER_BLOCK_D(D, 4)>
HWY_API void LoadInterleaved3(D d, const TFromD<D>* HWY_RESTRICT unaligned,
                              VFromD<D>& v0, VFromD<D>& v1, VFromD<D>& v2) {
  using V = VFromD<D>;
  V A;  // v0[1] v2[0] v1[0] v0[0]
  V B;  // v1[2] v0[2] v2[1] v1[1]
  V C;  // v2[3] v1[3] v0[3] v2[2]
  detail::LoadTransposedBlocks3(d, unaligned, A, B, C);

  const V vxx_02_03_xx = OddEven(C, B);
  v0 = detail::ShuffleTwo1230(A, vxx_02_03_xx);

  // Shuffle2301 takes the upper/lower halves of the output from one input, so
  // we cannot just combine 13 and 10 with 12 and 11 (similar to v0/v2). Use
  // OddEven because it may have higher throughput than Shuffle.
  const V vxx_xx_10_11 = OddEven(A, B);
  const V v12_13_xx_xx = OddEven(B, C);
  v1 = detail::ShuffleTwo2301(vxx_xx_10_11, v12_13_xx_xx);

  const V vxx_20_21_xx = OddEven(B, A);
  v2 = detail::ShuffleTwo3012(vxx_20_21_xx, C);
}

template <class D, HWY_IF_LANES_PER_BLOCK_D(D, 2)>
HWY_API void LoadInterleaved3(D d, const TFromD<D>* HWY_RESTRICT unaligned,
                              VFromD<D>& v0, VFromD<D>& v1, VFromD<D>& v2) {
  VFromD<D> A;  // v1[0] v0[0]
  VFromD<D> B;  // v0[1] v2[0]
  VFromD<D> C;  // v2[1] v1[1]
  detail::LoadTransposedBlocks3(d, unaligned, A, B, C);
  v0 = OddEven(B, A);
  v1 = CombineShiftRightBytes<sizeof(TFromD<D>)>(d, C, A);
  v2 = OddEven(C, B);
}

template <class D, typename T = TFromD<D>, HWY_IF_LANES_D(D, 1)>
HWY_API void LoadInterleaved3(D d, const T* HWY_RESTRICT unaligned,
                              VFromD<D>& v0, VFromD<D>& v1, VFromD<D>& v2) {
  v0 = LoadU(d, unaligned + 0);
  v1 = LoadU(d, unaligned + 1);
  v2 = LoadU(d, unaligned + 2);
}

// ------------------------------ LoadInterleaved4

namespace detail {

// Default for <= 128-bit vectors; x86_256 and x86_512 have their own overload.
template <class D, HWY_IF_V_SIZE_LE_D(D, 16)>
HWY_INLINE void LoadTransposedBlocks4(D d,
                                      const TFromD<D>* HWY_RESTRICT unaligned,
                                      VFromD<D>& vA, VFromD<D>& vB,
                                      VFromD<D>& vC, VFromD<D>& vD) {
  constexpr size_t kN = MaxLanes(d);
  vA = LoadU(d, unaligned + 0 * kN);
  vB = LoadU(d, unaligned + 1 * kN);
  vC = LoadU(d, unaligned + 2 * kN);
  vD = LoadU(d, unaligned + 3 * kN);
}

}  // namespace detail

template <class D, HWY_IF_LANES_PER_BLOCK_D(D, 16)>
HWY_API void LoadInterleaved4(D d, const TFromD<D>* HWY_RESTRICT unaligned,
                              VFromD<D>& v0, VFromD<D>& v1, VFromD<D>& v2,
                              VFromD<D>& v3) {
  const Repartition<uint64_t, decltype(d)> d64;
  using V64 = VFromD<decltype(d64)>;
  using V = VFromD<D>;
  // 16 lanes per block; the lowest four blocks are at the bottom of vA..vD.
  // Here int[i] means the four interleaved values of the i-th 4-tuple and
  // int[3..0] indicates four consecutive 4-tuples (0 = least-significant).
  V vA;  // int[13..10] int[3..0]
  V vB;  // int[17..14] int[7..4]
  V vC;  // int[1b..18] int[b..8]
  V vD;  // int[1f..1c] int[f..c]
  detail::LoadTransposedBlocks4(d, unaligned, vA, vB, vC, vD);

  // For brevity, the comments only list the lower block (upper = lower + 0x10)
  const V v5140 = InterleaveLower(d, vA, vB);  // int[5,1,4,0]
  const V vd9c8 = InterleaveLower(d, vC, vD);  // int[d,9,c,8]
  const V v7362 = InterleaveUpper(d, vA, vB);  // int[7,3,6,2]
  const V vfbea = InterleaveUpper(d, vC, vD);  // int[f,b,e,a]

  const V v6420 = InterleaveLower(d, v5140, v7362);  // int[6,4,2,0]
  const V veca8 = InterleaveLower(d, vd9c8, vfbea);  // int[e,c,a,8]
  const V v7531 = InterleaveUpper(d, v5140, v7362);  // int[7,5,3,1]
  const V vfdb9 = InterleaveUpper(d, vd9c8, vfbea);  // int[f,d,b,9]

  const V64 v10L = BitCast(d64, InterleaveLower(d, v6420, v7531));  // v10[7..0]
  const V64 v10U = BitCast(d64, InterleaveLower(d, veca8, vfdb9));  // v10[f..8]
  const V64 v32L = BitCast(d64, InterleaveUpper(d, v6420, v7531));  // v32[7..0]
  const V64 v32U = BitCast(d64, InterleaveUpper(d, veca8, vfdb9));  // v32[f..8]

  v0 = BitCast(d, InterleaveLower(d64, v10L, v10U));
  v1 = BitCast(d, InterleaveUpper(d64, v10L, v10U));
  v2 = BitCast(d, InterleaveLower(d64, v32L, v32U));
  v3 = BitCast(d, InterleaveUpper(d64, v32L, v32U));
}

template <class D, HWY_IF_LANES_PER_BLOCK_D(D, 8)>
HWY_API void LoadInterleaved4(D d, const TFromD<D>* HWY_RESTRICT unaligned,
                              VFromD<D>& v0, VFromD<D>& v1, VFromD<D>& v2,
                              VFromD<D>& v3) {
  // In the last step, we interleave by half of the block size, which is usually
  // 8 bytes but half that for 8-bit x8 vectors.
  using TW = hwy::UnsignedFromSize<d.MaxBytes() == 8 ? 4 : 8>;
  const Repartition<TW, decltype(d)> dw;
  using VW = VFromD<decltype(dw)>;

  // (Comments are for 256-bit vectors.)
  // 8 lanes per block; the lowest four blocks are at the bottom of vA..vD.
  VFromD<D> vA;  // v3210[9]v3210[8] v3210[1]v3210[0]
  VFromD<D> vB;  // v3210[b]v3210[a] v3210[3]v3210[2]
  VFromD<D> vC;  // v3210[d]v3210[c] v3210[5]v3210[4]
  VFromD<D> vD;  // v3210[f]v3210[e] v3210[7]v3210[6]
  detail::LoadTransposedBlocks4(d, unaligned, vA, vB, vC, vD);

  const VFromD<D> va820 = InterleaveLower(d, vA, vB);  // v3210[a,8] v3210[2,0]
  const VFromD<D> vec64 = InterleaveLower(d, vC, vD);  // v3210[e,c] v3210[6,4]
  const VFromD<D> vb931 = InterleaveUpper(d, vA, vB);  // v3210[b,9] v3210[3,1]
  const VFromD<D> vfd75 = InterleaveUpper(d, vC, vD);  // v3210[f,d] v3210[7,5]

  const VW v10_b830 =  // v10[b..8] v10[3..0]
      BitCast(dw, InterleaveLower(d, va820, vb931));
  const VW v10_fc74 =  // v10[f..c] v10[7..4]
      BitCast(dw, InterleaveLower(d, vec64, vfd75));
  const VW v32_b830 =  // v32[b..8] v32[3..0]
      BitCast(dw, InterleaveUpper(d, va820, vb931));
  const VW v32_fc74 =  // v32[f..c] v32[7..4]
      BitCast(dw, InterleaveUpper(d, vec64, vfd75));

  v0 = BitCast(d, InterleaveLower(dw, v10_b830, v10_fc74));
  v1 = BitCast(d, InterleaveUpper(dw, v10_b830, v10_fc74));
  v2 = BitCast(d, InterleaveLower(dw, v32_b830, v32_fc74));
  v3 = BitCast(d, InterleaveUpper(dw, v32_b830, v32_fc74));
}

template <class D, HWY_IF_LANES_PER_BLOCK_D(D, 4)>
HWY_API void LoadInterleaved4(D d, const TFromD<D>* HWY_RESTRICT unaligned,
                              VFromD<D>& v0, VFromD<D>& v1, VFromD<D>& v2,
                              VFromD<D>& v3) {
  using V = VFromD<D>;
  V vA;  // v3210[4] v3210[0]
  V vB;  // v3210[5] v3210[1]
  V vC;  // v3210[6] v3210[2]
  V vD;  // v3210[7] v3210[3]
  detail::LoadTransposedBlocks4(d, unaligned, vA, vB, vC, vD);
  const V v10e = InterleaveLower(d, vA, vC);  // v1[6,4] v0[6,4] v1[2,0] v0[2,0]
  const V v10o = InterleaveLower(d, vB, vD);  // v1[7,5] v0[7,5] v1[3,1] v0[3,1]
  const V v32e = InterleaveUpper(d, vA, vC);  // v3[6,4] v2[6,4] v3[2,0] v2[2,0]
  const V v32o = InterleaveUpper(d, vB, vD);  // v3[7,5] v2[7,5] v3[3,1] v2[3,1]

  v0 = InterleaveLower(d, v10e, v10o);
  v1 = InterleaveUpper(d, v10e, v10o);
  v2 = InterleaveLower(d, v32e, v32o);
  v3 = InterleaveUpper(d, v32e, v32o);
}

template <class D, HWY_IF_LANES_PER_BLOCK_D(D, 2)>
HWY_API void LoadInterleaved4(D d, const TFromD<D>* HWY_RESTRICT unaligned,
                              VFromD<D>& v0, VFromD<D>& v1, VFromD<D>& v2,
                              VFromD<D>& v3) {
  VFromD<D> vA, vB, vC, vD;
  detail::LoadTransposedBlocks4(d, unaligned, vA, vB, vC, vD);
  v0 = InterleaveLower(d, vA, vC);
  v1 = InterleaveUpper(d, vA, vC);
  v2 = InterleaveLower(d, vB, vD);
  v3 = InterleaveUpper(d, vB, vD);
}

// Any T x1
template <class D, typename T = TFromD<D>, HWY_IF_LANES_D(D, 1)>
HWY_API void LoadInterleaved4(D d, const T* HWY_RESTRICT unaligned,
                              VFromD<D>& v0, VFromD<D>& v1, VFromD<D>& v2,
                              VFromD<D>& v3) {
  v0 = LoadU(d, unaligned + 0);
  v1 = LoadU(d, unaligned + 1);
  v2 = LoadU(d, unaligned + 2);
  v3 = LoadU(d, unaligned + 3);
}

// ------------------------------ StoreInterleaved2

namespace detail {

// Default for <= 128-bit vectors; x86_256 and x86_512 have their own overload.
template <class D, HWY_IF_V_SIZE_LE_D(D, 16)>
HWY_INLINE void StoreTransposedBlocks2(VFromD<D> A, VFromD<D> B, D d,
                                       TFromD<D>* HWY_RESTRICT unaligned) {
  constexpr size_t kN = MaxLanes(d);
  StoreU(A, d, unaligned + 0 * kN);
  StoreU(B, d, unaligned + 1 * kN);
}

}  // namespace detail

// >= 128 bit vector
template <class D, HWY_IF_V_SIZE_GT_D(D, 8)>
HWY_API void StoreInterleaved2(VFromD<D> v0, VFromD<D> v1, D d,
                               TFromD<D>* HWY_RESTRICT unaligned) {
  const auto v10L = InterleaveLower(d, v0, v1);  // .. v1[0] v0[0]
  const auto v10U = InterleaveUpper(d, v0, v1);  // .. v1[kN/2] v0[kN/2]
  detail::StoreTransposedBlocks2(v10L, v10U, d, unaligned);
}

// <= 64 bits
template <class V, class D, HWY_IF_V_SIZE_LE_D(D, 8)>
HWY_API void StoreInterleaved2(V part0, V part1, D d,
                               TFromD<D>* HWY_RESTRICT unaligned) {
  const Twice<decltype(d)> d2;
  const auto v0 = ZeroExtendVector(d2, part0);
  const auto v1 = ZeroExtendVector(d2, part1);
  const auto v10 = InterleaveLower(d2, v0, v1);
  StoreU(v10, d2, unaligned);
}

// ------------------------------ StoreInterleaved3 (CombineShiftRightBytes,
// TableLookupBytes)

namespace detail {

// Default for <= 128-bit vectors; x86_256 and x86_512 have their own overload.
template <class D, HWY_IF_V_SIZE_LE_D(D, 16)>
HWY_INLINE void StoreTransposedBlocks3(VFromD<D> A, VFromD<D> B, VFromD<D> C,
                                       D d, TFromD<D>* HWY_RESTRICT unaligned) {
  constexpr size_t kN = MaxLanes(d);
  StoreU(A, d, unaligned + 0 * kN);
  StoreU(B, d, unaligned + 1 * kN);
  StoreU(C, d, unaligned + 2 * kN);
}

}  // namespace detail

// >= 128-bit vector, 8-bit lanes
template <class D, HWY_IF_T_SIZE_D(D, 1), HWY_IF_V_SIZE_GT_D(D, 8)>
HWY_API void StoreInterleaved3(VFromD<D> v0, VFromD<D> v1, VFromD<D> v2, D d,
                               TFromD<D>* HWY_RESTRICT unaligned) {
  const RebindToUnsigned<decltype(d)> du;
  using TU = TFromD<decltype(du)>;
  const auto k5 = Set(du, TU{5});
  const auto k6 = Set(du, TU{6});

  // Interleave (v0,v1,v2) to (MSB on left, lane 0 on right):
  // v0[5], v2[4],v1[4],v0[4] .. v2[0],v1[0],v0[0]. We're expanding v0 lanes
  // to their place, with 0x80 so lanes to be filled from other vectors are 0
  // to enable blending by ORing together.
  const VFromD<decltype(du)> shuf_A0 =
      Dup128VecFromValues(du, 00x80, 0x80, 10x80, 0x80, 20x80, 0x80, 3,
                          0x80, 0x80, 40x80, 0x80, 5);
  // Cannot reuse shuf_A0 because it contains 5.
  const VFromD<decltype(du)> shuf_A1 =
      Dup128VecFromValues(du, 0x80, 00x80, 0x80, 10x80, 0x80, 20x80, 0x80,
                          30x80, 0x80, 40x80, 0x80);
  // The interleaved vectors will be named A, B, C; temporaries with suffix
  // 0..2 indicate which input vector's lanes they hold.
  // cannot reuse shuf_A0 (has 5)
  const auto shuf_A2 = CombineShiftRightBytes<15>(du, shuf_A1, shuf_A1);
  const auto A0 = TableLookupBytesOr0(v0, shuf_A0);  // 5..4..3..2..1..0
  const auto A1 = TableLookupBytesOr0(v1, shuf_A1);  // ..4..3..2..1..0.
  const auto A2 = TableLookupBytesOr0(v2, shuf_A2);  // .4..3..2..1..0..
  const VFromD<D> A = BitCast(d, A0 | A1 | A2);

  // B: v1[10],v0[10], v2[9],v1[9],v0[9] .. , v2[6],v1[6],v0[6], v2[5],v1[5]
  const auto shuf_B0 = shuf_A2 + k6;  // .A..9..8..7..6..
  const auto shuf_B1 = shuf_A0 + k5;  // A..9..8..7..6..5
  const auto shuf_B2 = shuf_A1 + k5;  // ..9..8..7..6..5.
  const auto B0 = TableLookupBytesOr0(v0, shuf_B0);
  const auto B1 = TableLookupBytesOr0(v1, shuf_B1);
  const auto B2 = TableLookupBytesOr0(v2, shuf_B2);
  const VFromD<D> B = BitCast(d, B0 | B1 | B2);

  // C: v2[15],v1[15],v0[15], v2[11],v1[11],v0[11], v2[10]
  const auto shuf_C0 = shuf_B2 + k6;  // ..F..E..D..C..B.
  const auto shuf_C1 = shuf_B0 + k5;  // .F..E..D..C..B..
  const auto shuf_C2 = shuf_B1 + k5;  // F..E..D..C..B..A
  const auto C0 = TableLookupBytesOr0(v0, shuf_C0);
  const auto C1 = TableLookupBytesOr0(v1, shuf_C1);
  const auto C2 = TableLookupBytesOr0(v2, shuf_C2);
  const VFromD<D> C = BitCast(d, C0 | C1 | C2);

  detail::StoreTransposedBlocks3(A, B, C, d, unaligned);
}

// >= 128-bit vector, 16-bit lanes
template <class D, HWY_IF_T_SIZE_D(D, 2), HWY_IF_V_SIZE_GT_D(D, 8)>
HWY_API void StoreInterleaved3(VFromD<D> v0, VFromD<D> v1, VFromD<D> v2, D d,
                               TFromD<D>* HWY_RESTRICT unaligned) {
  const Repartition<uint8_t, decltype(d)> du8;
  const auto k2 = Set(du8, uint8_t{2 * sizeof(TFromD<D>)});
  const auto k3 = Set(du8, uint8_t{3 * sizeof(TFromD<D>)});

  // Interleave (v0,v1,v2) to (MSB on left, lane 0 on right):
  // v1[2],v0[2], v2[1],v1[1],v0[1], v2[0],v1[0],v0[0]. 0x80 so lanes to be
  // filled from other vectors are 0 for blending. Note that these are byte
  // indices for 16-bit lanes.
  const VFromD<decltype(du8)> shuf_A1 =
      Dup128VecFromValues(du8, 0x80, 0x80, 010x80, 0x80, 0x80, 0x80, 23,
                          0x80, 0x80, 0x80, 0x80, 45);
  const VFromD<decltype(du8)> shuf_A2 =
      Dup128VecFromValues(du8, 0x80, 0x80, 0x80, 0x80, 010x80, 0x80, 0x80,
                          0x80, 230x80, 0x80, 0x80, 0x80);

  // The interleaved vectors will be named A, B, C; temporaries with suffix
  // 0..2 indicate which input vector's lanes they hold.
  const auto shuf_A0 = CombineShiftRightBytes<2>(du8, shuf_A1, shuf_A1);

  const auto A0 = TableLookupBytesOr0(v0, shuf_A0);
  const auto A1 = TableLookupBytesOr0(v1, shuf_A1);
  const auto A2 = TableLookupBytesOr0(v2, shuf_A2);
  const VFromD<D> A = BitCast(d, A0 | A1 | A2);

  // B: v0[5] v2[4],v1[4],v0[4], v2[3],v1[3],v0[3], v2[2]
  const auto shuf_B0 = shuf_A1 + k3;  // 5..4..3.
  const auto shuf_B1 = shuf_A2 + k3;  // ..4..3..
  const auto shuf_B2 = shuf_A0 + k2;  // .4..3..2
  const auto B0 = TableLookupBytesOr0(v0, shuf_B0);
  const auto B1 = TableLookupBytesOr0(v1, shuf_B1);
  const auto B2 = TableLookupBytesOr0(v2, shuf_B2);
  const VFromD<D> B = BitCast(d, B0 | B1 | B2);

  // C: v2[7],v1[7],v0[7], v2[6],v1[6],v0[6], v2[5],v1[5]
  const auto shuf_C0 = shuf_B1 + k3;  // ..7..6..
  const auto shuf_C1 = shuf_B2 + k3;  // .7..6..5
  const auto shuf_C2 = shuf_B0 + k2;  // 7..6..5.
  const auto C0 = TableLookupBytesOr0(v0, shuf_C0);
  const auto C1 = TableLookupBytesOr0(v1, shuf_C1);
  const auto C2 = TableLookupBytesOr0(v2, shuf_C2);
  const VFromD<D> C = BitCast(d, C0 | C1 | C2);

  detail::StoreTransposedBlocks3(A, B, C, d, unaligned);
}

// >= 128-bit vector, 32-bit lanes
template <class D, HWY_IF_T_SIZE_D(D, 4), HWY_IF_V_SIZE_GT_D(D, 8)>
HWY_API void StoreInterleaved3(VFromD<D> v0, VFromD<D> v1, VFromD<D> v2, D d,
                               TFromD<D>* HWY_RESTRICT unaligned) {
  const RepartitionToWide<decltype(d)> dw;

  const VFromD<D> v10_v00 = InterleaveLower(d, v0, v1);
  const VFromD<D> v01_v20 = OddEven(v0, v2);
  // A: v0[1], v2[0],v1[0],v0[0] (<- lane 0)
  const VFromD<D> A = BitCast(
      d, InterleaveLower(dw, BitCast(dw, v10_v00), BitCast(dw, v01_v20)));

  const VFromD<D> v1_321 = ShiftRightLanes<1>(d, v1);
  const VFromD<D> v0_32 = ShiftRightLanes<2>(d, v0);
  const VFromD<D> v21_v11 = OddEven(v2, v1_321);
  const VFromD<D> v12_v02 = OddEven(v1_321, v0_32);
  // B: v1[2],v0[2], v2[1],v1[1]
  const VFromD<D> B = BitCast(
      d, InterleaveLower(dw, BitCast(dw, v21_v11), BitCast(dw, v12_v02)));

  // Notation refers to the upper 2 lanes of the vector for InterleaveUpper.
  const VFromD<D> v23_v13 = OddEven(v2, v1_321);
  const VFromD<D> v03_v22 = OddEven(v0, v2);
  // C: v2[3],v1[3],v0[3], v2[2]
  const VFromD<D> C = BitCast(
      d, InterleaveUpper(dw, BitCast(dw, v03_v22), BitCast(dw, v23_v13)));

  detail::StoreTransposedBlocks3(A, B, C, d, unaligned);
}

// >= 128-bit vector, 64-bit lanes
template <class D, HWY_IF_T_SIZE_D(D, 8), HWY_IF_V_SIZE_GT_D(D, 8)>
HWY_API void StoreInterleaved3(VFromD<D> v0, VFromD<D> v1, VFromD<D> v2, D d,
                               TFromD<D>* HWY_RESTRICT unaligned) {
  const VFromD<D> A = InterleaveLower(d, v0, v1);
  const VFromD<D> B = OddEven(v0, v2);
  const VFromD<D> C = InterleaveUpper(d, v1, v2);
  detail::StoreTransposedBlocks3(A, B, C, d, unaligned);
}

// 64-bit vector, 8-bit lanes
template <class D, HWY_IF_T_SIZE_D(D, 1), HWY_IF_V_SIZE_D(D, 8)>
HWY_API void StoreInterleaved3(VFromD<D> part0, VFromD<D> part1,
                               VFromD<D> part2, D d,
                               TFromD<D>* HWY_RESTRICT unaligned) {
  // Use full vectors for the shuffles and first result.
  constexpr size_t kFullN = 16 / sizeof(TFromD<D>);
  const Full128<uint8_t> du;
  const Full128<TFromD<D>> d_full;
  const auto k5 = Set(du, uint8_t{5});
  const auto k6 = Set(du, uint8_t{6});

  const VFromD<decltype(d_full)> v0{part0.raw};
  const VFromD<decltype(d_full)> v1{part1.raw};
  const VFromD<decltype(d_full)> v2{part2.raw};

  // Interleave (v0,v1,v2) to (MSB on left, lane 0 on right):
  // v1[2],v0[2], v2[1],v1[1],v0[1], v2[0],v1[0],v0[0]. 0x80 so lanes to be
  // filled from other vectors are 0 for blending.
  alignas(16static constexpr uint8_t tbl_v0[16] = {
      00x80, 0x80, 10x80, 0x80, 20x80, 0x80,  //
      30x80, 0x80, 40x80, 0x80, 5};
  alignas(16static constexpr uint8_t tbl_v1[16] = {
      0x80, 00x80, 0x80, 10x80,  //
      0x80, 20x80, 0x80, 30x80, 0x80, 40x80, 0x80};
  // The interleaved vectors will be named A, B, C; temporaries with suffix
  // 0..2 indicate which input vector's lanes they hold.
  const auto shuf_A0 = Load(du, tbl_v0);
  const auto shuf_A1 = Load(du, tbl_v1);  // cannot reuse shuf_A0 (5 in MSB)
  const auto shuf_A2 = CombineShiftRightBytes<15>(du, shuf_A1, shuf_A1);
  const auto A0 = TableLookupBytesOr0(v0, shuf_A0);  // 5..4..3..2..1..0
  const auto A1 = TableLookupBytesOr0(v1, shuf_A1);  // ..4..3..2..1..0.
  const auto A2 = TableLookupBytesOr0(v2, shuf_A2);  // .4..3..2..1..0..
  const auto A = BitCast(d_full, A0 | A1 | A2);
  StoreU(A, d_full, unaligned + 0 * kFullN);

  // Second (HALF) vector: v2[7],v1[7],v0[7], v2[6],v1[6],v0[6], v2[5],v1[5]
  const auto shuf_B0 = shuf_A2 + k6;  // ..7..6..
  const auto shuf_B1 = shuf_A0 + k5;  // .7..6..5
  const auto shuf_B2 = shuf_A1 + k5;  // 7..6..5.
  const auto B0 = TableLookupBytesOr0(v0, shuf_B0);
  const auto B1 = TableLookupBytesOr0(v1, shuf_B1);
  const auto B2 = TableLookupBytesOr0(v2, shuf_B2);
  const VFromD<D> B{BitCast(d_full, B0 | B1 | B2).raw};
  StoreU(B, d, unaligned + 1 * kFullN);
}

// 64-bit vector, 16-bit lanes
template <class D, HWY_IF_T_SIZE_D(D, 2), HWY_IF_LANES_D(D, 4)>
HWY_API void StoreInterleaved3(VFromD<D> part0, VFromD<D> part1,
                               VFromD<D> part2, D dh,
                               TFromD<D>* HWY_RESTRICT unaligned) {
  const Twice<D> d_full;
  const Full128<uint8_t> du8;
  const auto k2 = Set(du8, uint8_t{2 * sizeof(TFromD<D>)});
  const auto k3 = Set(du8, uint8_t{3 * sizeof(TFromD<D>)});

  const VFromD<decltype(d_full)> v0{part0.raw};
  const VFromD<decltype(d_full)> v1{part1.raw};
  const VFromD<decltype(d_full)> v2{part2.raw};

  // Interleave part (v0,v1,v2) to full (MSB on left, lane 0 on right):
  // v1[2],v0[2], v2[1],v1[1],v0[1], v2[0],v1[0],v0[0]. We're expanding v0 lanes
  // to their place, with 0x80 so lanes to be filled from other vectors are 0
  // to enable blending by ORing together.
  alignas(16static constexpr uint8_t tbl_v1[16] = {
      0x80, 0x80, 0,    1,    0x80, 0x80, 0x80, 0x80,
      2,    3,    0x80, 0x80, 0x80, 0x80, 4,    5};
  alignas(16static constexpr uint8_t tbl_v2[16] = {
      0x80, 0x80, 0x80, 0x80, 0,    1,    0x80, 0x80,
      0x80, 0x80, 2,    3,    0x80, 0x80, 0x80, 0x80};

  // The interleaved vectors will be named A, B; temporaries with suffix
  // 0..2 indicate which input vector's lanes they hold.
  const auto shuf_A1 = Load(du8, tbl_v1);  // 2..1..0.
                                           // .2..1..0
  const auto shuf_A0 = CombineShiftRightBytes<2>(du8, shuf_A1, shuf_A1);
  const auto shuf_A2 = Load(du8, tbl_v2);  // ..1..0..

  const auto A0 = TableLookupBytesOr0(v0, shuf_A0);
  const auto A1 = TableLookupBytesOr0(v1, shuf_A1);
  const auto A2 = TableLookupBytesOr0(v2, shuf_A2);
  const VFromD<decltype(d_full)> A = BitCast(d_full, A0 | A1 | A2);
  StoreU(A, d_full, unaligned);

  // Second (HALF) vector: v2[3],v1[3],v0[3], v2[2]
  const auto shuf_B0 = shuf_A1 + k3;  // ..3.
  const auto shuf_B1 = shuf_A2 + k3;  // .3..
  const auto shuf_B2 = shuf_A0 + k2;  // 3..2
  const auto B0 = TableLookupBytesOr0(v0, shuf_B0);
  const auto B1 = TableLookupBytesOr0(v1, shuf_B1);
  const auto B2 = TableLookupBytesOr0(v2, shuf_B2);
  const VFromD<decltype(d_full)> B = BitCast(d_full, B0 | B1 | B2);
  StoreU(VFromD<D>{B.raw}, dh, unaligned + MaxLanes(d_full));
}

// 64-bit vector, 32-bit lanes
template <class D, HWY_IF_T_SIZE_D(D, 4), HWY_IF_LANES_D(D, 2)>
HWY_API void StoreInterleaved3(VFromD<D> v0, VFromD<D> v1, VFromD<D> v2, D d,
                               TFromD<D>* HWY_RESTRICT unaligned) {
  // (same code as 128-bit vector, 64-bit lanes)
  const VFromD<D> v10_v00 = InterleaveLower(d, v0, v1);
  const VFromD<D> v01_v20 = OddEven(v0, v2);
  const VFromD<D> v21_v11 = InterleaveUpper(d, v1, v2);
  constexpr size_t kN = MaxLanes(d);
  StoreU(v10_v00, d, unaligned + 0 * kN);
  StoreU(v01_v20, d, unaligned + 1 * kN);
  StoreU(v21_v11, d, unaligned + 2 * kN);
}

// 64-bit lanes are handled by the N=1 case below.

// <= 32-bit vector, 8-bit lanes
template <class D, HWY_IF_T_SIZE_D(D, 1), HWY_IF_V_SIZE_LE_D(D, 4),
          HWY_IF_LANES_GT_D(D, 1)>
HWY_API void StoreInterleaved3(VFromD<D> part0, VFromD<D> part1,
                               VFromD<D> part2, D d,
                               TFromD<D>* HWY_RESTRICT unaligned) {
  // Use full vectors for the shuffles and result.
  const Full128<uint8_t> du;
  const Full128<TFromD<D>> d_full;

  const VFromD<decltype(d_full)> v0{part0.raw};
  const VFromD<decltype(d_full)> v1{part1.raw};
  const VFromD<decltype(d_full)> v2{part2.raw};

  // Interleave (v0,v1,v2). We're expanding v0 lanes to their place, with 0x80
  // so lanes to be filled from other vectors are 0 to enable blending by ORing
  // together.
  alignas(16static constexpr uint8_t tbl_v0[16] = {
      0,    0x80, 0x80, 1,    0x80, 0x80, 2,    0x80,
      0x80, 3,    0x80, 0x80, 0x80, 0x80, 0x80, 0x80};
  // The interleaved vector will be named A; temporaries with suffix
  // 0..2 indicate which input vector's lanes they hold.
  const auto shuf_A0 = Load(du, tbl_v0);
  const auto shuf_A1 = CombineShiftRightBytes<15>(du, shuf_A0, shuf_A0);
  const auto shuf_A2 = CombineShiftRightBytes<14>(du, shuf_A0, shuf_A0);
  const auto A0 = TableLookupBytesOr0(v0, shuf_A0);  // ......3..2..1..0
  const auto A1 = TableLookupBytesOr0(v1, shuf_A1);  // .....3..2..1..0.
  const auto A2 = TableLookupBytesOr0(v2, shuf_A2);  // ....3..2..1..0..
  const VFromD<decltype(d_full)> A = BitCast(d_full, A0 | A1 | A2);
  alignas(16) TFromD<D> buf[MaxLanes(d_full)];
  StoreU(A, d_full, buf);
  CopyBytes<d.MaxBytes() * 3>(buf, unaligned);
}

// 32-bit vector, 16-bit lanes
template <class D, HWY_IF_T_SIZE_D(D, 2), HWY_IF_LANES_D(D, 2)>
HWY_API void StoreInterleaved3(VFromD<D> part0, VFromD<D> part1,
                               VFromD<D> part2, D d,
                               TFromD<D>* HWY_RESTRICT unaligned) {
  // Use full vectors for the shuffles and result.
  const Full128<uint8_t> du8;
  const Full128<TFromD<D>> d_full;

  const VFromD<decltype(d_full)> v0{part0.raw};
  const VFromD<decltype(d_full)> v1{part1.raw};
  const VFromD<decltype(d_full)> v2{part2.raw};

  // Interleave (v0,v1,v2). We're expanding v0 lanes to their place, with 0x80
  // so lanes to be filled from other vectors are 0 to enable blending by ORing
  // together.
  alignas(16static constexpr uint8_t tbl_v2[16] = {
      0x80, 0x80, 0x80, 0x80, 0,    1,    0x80, 0x80,
      0x80, 0x80, 2,    3,    0x80, 0x80, 0x80, 0x80};
  // The interleaved vector will be named A; temporaries with suffix
  // 0..2 indicate which input vector's lanes they hold.
  const auto shuf_A2 =  // ..1..0..
      Load(du8, tbl_v2);
  const auto shuf_A1 =  // ...1..0.
      CombineShiftRightBytes<2>(du8, shuf_A2, shuf_A2);
  const auto shuf_A0 =  // ....1..0
      CombineShiftRightBytes<4>(du8, shuf_A2, shuf_A2);
  const auto A0 = TableLookupBytesOr0(v0, shuf_A0);  // ..1..0
  const auto A1 = TableLookupBytesOr0(v1, shuf_A1);  // .1..0.
  const auto A2 = TableLookupBytesOr0(v2, shuf_A2);  // 1..0..
  const auto A = BitCast(d_full, A0 | A1 | A2);
  alignas(16) TFromD<D> buf[MaxLanes(d_full)];
  StoreU(A, d_full, buf);
  CopyBytes<d.MaxBytes() * 3>(buf, unaligned);
}

// Single-element vector, any lane size: just store directly
template <class D, HWY_IF_LANES_D(D, 1)>
HWY_API void StoreInterleaved3(VFromD<D> v0, VFromD<D> v1, VFromD<D> v2, D d,
                               TFromD<D>* HWY_RESTRICT unaligned) {
  StoreU(v0, d, unaligned + 0);
  StoreU(v1, d, unaligned + 1);
  StoreU(v2, d, unaligned + 2);
}

// ------------------------------ StoreInterleaved4

namespace detail {

// Default for <= 128-bit vectors; x86_256 and x86_512 have their own overload.
template <class D, HWY_IF_V_SIZE_LE_D(D, 16)>
HWY_INLINE void StoreTransposedBlocks4(VFromD<D> vA, VFromD<D> vB, VFromD<D> vC,
                                       VFromD<D> vD, D d,
                                       TFromD<D>* HWY_RESTRICT unaligned) {
  constexpr size_t kN = MaxLanes(d);
  StoreU(vA, d, unaligned + 0 * kN);
  StoreU(vB, d, unaligned + 1 * kN);
  StoreU(vC, d, unaligned + 2 * kN);
  StoreU(vD, d, unaligned + 3 * kN);
}

}  // namespace detail

// >= 128-bit vector, 8..32-bit lanes
template <class D, HWY_IF_NOT_T_SIZE_D(D, 8), HWY_IF_V_SIZE_GT_D(D, 8)>
HWY_API void StoreInterleaved4(VFromD<D> v0, VFromD<D> v1, VFromD<D> v2,
                               VFromD<D> v3, D d,
                               TFromD<D>* HWY_RESTRICT unaligned) {
  const RepartitionToWide<decltype(d)> dw;
  const auto v10L = ZipLower(dw, v0, v1);  // .. v1[0] v0[0]
  const auto v32L = ZipLower(dw, v2, v3);
  const auto v10U = ZipUpper(dw, v0, v1);
  const auto v32U = ZipUpper(dw, v2, v3);
  // The interleaved vectors are vA, vB, vC, vD.
  const VFromD<D> vA = BitCast(d, InterleaveLower(dw, v10L, v32L));  // 3210
  const VFromD<D> vB = BitCast(d, InterleaveUpper(dw, v10L, v32L));
  const VFromD<D> vC = BitCast(d, InterleaveLower(dw, v10U, v32U));
  const VFromD<D> vD = BitCast(d, InterleaveUpper(dw, v10U, v32U));
  detail::StoreTransposedBlocks4(vA, vB, vC, vD, d, unaligned);
}

// >= 128-bit vector, 64-bit lanes
template <class D, HWY_IF_T_SIZE_D(D, 8), HWY_IF_V_SIZE_GT_D(D, 8)>
HWY_API void StoreInterleaved4(VFromD<D> v0, VFromD<D> v1, VFromD<D> v2,
                               VFromD<D> v3, D d,
                               TFromD<D>* HWY_RESTRICT unaligned) {
  // The interleaved vectors are vA, vB, vC, vD.
  const VFromD<D> vA = InterleaveLower(d, v0, v1);  // v1[0] v0[0]
  const VFromD<D> vB = InterleaveLower(d, v2, v3);
  const VFromD<D> vC = InterleaveUpper(d, v0, v1);
  const VFromD<D> vD = InterleaveUpper(d, v2, v3);
  detail::StoreTransposedBlocks4(vA, vB, vC, vD, d, unaligned);
}

// 64-bit vector, 8..32-bit lanes
template <class D, HWY_IF_NOT_T_SIZE_D(D, 8), HWY_IF_V_SIZE_D(D, 8)>
HWY_API void StoreInterleaved4(VFromD<D> part0, VFromD<D> part1,
                               VFromD<D> part2, VFromD<D> part3, D /* tag */,
                               TFromD<D>* HWY_RESTRICT unaligned) {
  // Use full vectors to reduce the number of stores.
  const Full128<TFromD<D>> d_full;
  const RepartitionToWide<decltype(d_full)> dw;
  const VFromD<decltype(d_full)> v0{part0.raw};
  const VFromD<decltype(d_full)> v1{part1.raw};
  const VFromD<decltype(d_full)> v2{part2.raw};
  const VFromD<decltype(d_full)> v3{part3.raw};
  const auto v10 = ZipLower(dw, v0, v1);  // v1[0] v0[0]
  const auto v32 = ZipLower(dw, v2, v3);
  const auto A = BitCast(d_full, InterleaveLower(dw, v10, v32));
  const auto B = BitCast(d_full, InterleaveUpper(dw, v10, v32));
  StoreU(A, d_full, unaligned);
  StoreU(B, d_full, unaligned + MaxLanes(d_full));
}

// 64-bit vector, 64-bit lane
template <class D, HWY_IF_T_SIZE_D(D, 8), HWY_IF_LANES_D(D, 1)>
HWY_API void StoreInterleaved4(VFromD<D> part0, VFromD<D> part1,
                               VFromD<D> part2, VFromD<D> part3, D /* tag */,
                               TFromD<D>* HWY_RESTRICT unaligned) {
  // Use full vectors to reduce the number of stores.
  const Full128<TFromD<D>> d_full;
  const VFromD<decltype(d_full)> v0{part0.raw};
  const VFromD<decltype(d_full)> v1{part1.raw};
  const VFromD<decltype(d_full)> v2{part2.raw};
  const VFromD<decltype(d_full)> v3{part3.raw};
  const auto A = InterleaveLower(d_full, v0, v1);  // v1[0] v0[0]
  const auto B = InterleaveLower(d_full, v2, v3);
  StoreU(A, d_full, unaligned);
  StoreU(B, d_full, unaligned + MaxLanes(d_full));
}

// <= 32-bit vectors
template <class D, HWY_IF_V_SIZE_LE_D(D, 4)>
HWY_API void StoreInterleaved4(VFromD<D> part0, VFromD<D> part1,
                               VFromD<D> part2, VFromD<D> part3, D d,
                               TFromD<D>* HWY_RESTRICT unaligned) {
  // Use full vectors to reduce the number of stores.
  const Full128<TFromD<D>> d_full;
  const RepartitionToWide<decltype(d_full)> dw;
  const VFromD<decltype(d_full)> v0{part0.raw};
  const VFromD<decltype(d_full)> v1{part1.raw};
  const VFromD<decltype(d_full)> v2{part2.raw};
  const VFromD<decltype(d_full)> v3{part3.raw};
  const auto v10 = ZipLower(dw, v0, v1);  // .. v1[0] v0[0]
  const auto v32 = ZipLower(dw, v2, v3);
  const auto v3210 = BitCast(d_full, InterleaveLower(dw, v10, v32));
  alignas(16) TFromD<D> buf[MaxLanes(d_full)];
  StoreU(v3210, d_full, buf);
  CopyBytes<d.MaxBytes() * 4>(buf, unaligned);
}

#endif  // HWY_NATIVE_LOAD_STORE_INTERLEAVED

// ------------------------------ LoadN

#if (defined(HWY_NATIVE_LOAD_N) == defined(HWY_TARGET_TOGGLE))

#ifdef HWY_NATIVE_LOAD_N
#undef HWY_NATIVE_LOAD_N
#else
#define HWY_NATIVE_LOAD_N
#endif

#if HWY_MEM_OPS_MIGHT_FAULT && !HWY_HAVE_SCALABLE
namespace detail {

template <class DTo, class DFrom>
HWY_INLINE VFromD<DTo> LoadNResizeBitCast(DTo d_to, DFrom d_from,
                                          VFromD<DFrom> v) {
#if HWY_TARGET <= HWY_SSE2
  // On SSE2/SSSE3/SSE4, the LoadU operation will zero out any lanes of v.raw
  // past the first (lowest-index) Lanes(d_from) lanes of v.raw if
  // sizeof(decltype(v.raw)) > d_from.MaxBytes() is true
  (void)d_from;
  return ResizeBitCast(d_to, v);
#else
  // On other targets such as PPC/NEON, the contents of any lanes past the first
  // (lowest-index) Lanes(d_from) lanes of v.raw might be non-zero if
  // sizeof(decltype(v.raw)) > d_from.MaxBytes() is true.
  return ZeroExtendResizeBitCast(d_to, d_from, v);
#endif
}

}  // namespace detail

template <class D, HWY_IF_V_SIZE_LE_D(D, 16), HWY_IF_LANES_D(D, 1),
          HWY_IF_NOT_BF16_D(D)>
HWY_API VFromD<D> LoadN(D d, const TFromD<D>* HWY_RESTRICT p,
                        size_t num_lanes) {
  return (num_lanes > 0) ? LoadU(d, p) : Zero(d);
}

template <class D, HWY_IF_V_SIZE_LE_D(D, 16), HWY_IF_LANES_D(D, 1),
          HWY_IF_NOT_BF16_D(D)>
HWY_API VFromD<D> LoadNOr(VFromD<D> no, D d, const TFromD<D>* HWY_RESTRICT p,
                          size_t num_lanes) {
  return (num_lanes > 0) ? LoadU(d, p) : no;
}

template <class D, HWY_IF_V_SIZE_LE_D(D, 16), HWY_IF_LANES_D(D, 2),
          HWY_IF_NOT_BF16_D(D)>
HWY_API VFromD<D> LoadN(D d, const TFromD<D>* HWY_RESTRICT p,
                        size_t num_lanes) {
  const FixedTag<TFromD<D>, 1> d1;

  if (num_lanes >= 2return LoadU(d, p);
  if (num_lanes == 0return Zero(d);
  return detail::LoadNResizeBitCast(d, d1, LoadU(d1, p));
}

template <class D, HWY_IF_V_SIZE_LE_D(D, 16), HWY_IF_LANES_D(D, 2),
          HWY_IF_NOT_BF16_D(D)>
HWY_API VFromD<D> LoadNOr(VFromD<D> no, D d, const TFromD<D>* HWY_RESTRICT p,
                          size_t num_lanes) {
  const FixedTag<TFromD<D>, 1> d1;

  if (num_lanes >= 2return LoadU(d, p);
  if (num_lanes == 0return no;
  return InterleaveLower(ResizeBitCast(d, LoadU(d1, p)), no);
}

template <class D, HWY_IF_V_SIZE_LE_D(D, 16), HWY_IF_LANES_D(D, 4),
          HWY_IF_NOT_BF16_D(D)>
HWY_API VFromD<D> LoadN(D d, const TFromD<D>* HWY_RESTRICT p,
                        size_t num_lanes) {
  const FixedTag<TFromD<D>, 2> d2;
  const Half<decltype(d2)> d1;

  if (num_lanes >= 4return LoadU(d, p);
  if (num_lanes == 0return Zero(d);
  if (num_lanes == 1return detail::LoadNResizeBitCast(d, d1, LoadU(d1, p));

  // Two or three lanes.
  const VFromD<D> v_lo = detail::LoadNResizeBitCast(d, d2, LoadU(d2, p));
  return (num_lanes == 2) ? v_lo : InsertLane(v_lo, 2, p[2]);
}

template <class D, HWY_IF_V_SIZE_LE_D(D, 16), HWY_IF_LANES_D(D, 4),
          HWY_IF_NOT_BF16_D(D)>
HWY_API VFromD<D> LoadNOr(VFromD<D> no, D d, const TFromD<D>* HWY_RESTRICT p,
                          size_t num_lanes) {
  const FixedTag<TFromD<D>, 2> d2;

  if (num_lanes >= 4return LoadU(d, p);
  if (num_lanes == 0return no;
  if (num_lanes == 1return InsertLane(no, 0, p[0]);

  // Two or three lanes.
  const VFromD<D> v_lo =
      ConcatUpperLower(d, no, ResizeBitCast(d, LoadU(d2, p)));
  return (num_lanes == 2) ? v_lo : InsertLane(v_lo, 2, p[2]);
}

template <class D, HWY_IF_V_SIZE_LE_D(D, 16), HWY_IF_LANES_D(D, 8),
          HWY_IF_NOT_BF16_D(D)>
HWY_API VFromD<D> LoadN(D d, const TFromD<D>* HWY_RESTRICT p,
                        size_t num_lanes) {
  const FixedTag<TFromD<D>, 4> d4;
  const Half<decltype(d4)> d2;
  const Half<decltype(d2)> d1;

  if (num_lanes >= 8return LoadU(d, p);
  if (num_lanes == 0return Zero(d);
  if (num_lanes == 1return detail::LoadNResizeBitCast(d, d1, LoadU(d1, p));

  const size_t leading_len = num_lanes & 4;
  VFromD<decltype(d4)> v_trailing = Zero(d4);

  if ((num_lanes & 2) != 0) {
    const VFromD<decltype(d2)> v_trailing_lo2 = LoadU(d2, p + leading_len);
    if ((num_lanes & 1) != 0) {
      v_trailing = Combine(
          d4,
          detail::LoadNResizeBitCast(d2, d1, LoadU(d1, p + leading_len + 2)),
          v_trailing_lo2);
    } else {
      v_trailing = detail::LoadNResizeBitCast(d4, d2, v_trailing_lo2);
    }
  } else if ((num_lanes & 1) != 0) {
    v_trailing = detail::LoadNResizeBitCast(d4, d1, LoadU(d1, p + leading_len));
  }

  if (leading_len != 0) {
    return Combine(d, v_trailing, LoadU(d4, p));
  } else {
    return detail::LoadNResizeBitCast(d, d4, v_trailing);
  }
}

template <class D, HWY_IF_V_SIZE_LE_D(D, 16), HWY_IF_LANES_D(D, 8),
          HWY_IF_NOT_BF16_D(D)>
HWY_API VFromD<D> LoadNOr(VFromD<D> no, D d, const TFromD<D>* HWY_RESTRICT p,
                          size_t num_lanes) {
  const FixedTag<TFromD<D>, 4> d4;
  const Half<decltype(d4)> d2;
  const Half<decltype(d2)> d1;

  if (num_lanes >= 8return LoadU(d, p);
  if (num_lanes == 0return no;
  if (num_lanes == 1return InsertLane(no, 0, p[0]);

  const size_t leading_len = num_lanes & 4;
  VFromD<decltype(d4)> v_trailing = ResizeBitCast(d4, no);

  if ((num_lanes & 2) != 0) {
    const VFromD<decltype(d2)> v_trailing_lo2 = LoadU(d2, p + leading_len);
    if ((num_lanes & 1) != 0) {
      v_trailing = Combine(
          d4,
          InterleaveLower(ResizeBitCast(d2, LoadU(d1, p + leading_len + 2)),
                          ResizeBitCast(d2, no)),
          v_trailing_lo2);
    } else {
      v_trailing = ConcatUpperLower(d4, ResizeBitCast(d4, no),
                                    ResizeBitCast(d4, v_trailing_lo2));
    }
  } else if ((num_lanes & 1) != 0) {
    v_trailing = InsertLane(ResizeBitCast(d4, no), 0, p[leading_len]);
  }

  if (leading_len != 0) {
    return Combine(d, v_trailing, LoadU(d4, p));
  } else {
    return ConcatUpperLower(d, no, ResizeBitCast(d, v_trailing));
  }
}

template <class D, HWY_IF_V_SIZE_LE_D(D, 16), HWY_IF_LANES_D(D, 16),
          HWY_IF_NOT_BF16_D(D)>
HWY_API VFromD<D> LoadN(D d, const TFromD<D>* HWY_RESTRICT p,
                        size_t num_lanes) {
  const FixedTag<TFromD<D>, 8> d8;
  const Half<decltype(d8)> d4;
  const Half<decltype(d4)> d2;
  const Half<decltype(d2)> d1;

  if (num_lanes >= 16return LoadU(d, p);
  if (num_lanes == 0return Zero(d);
  if (num_lanes == 1return detail::LoadNResizeBitCast(d, d1, LoadU(d1, p));

  const size_t leading_len = num_lanes & 12;
  VFromD<decltype(d4)> v_trailing = Zero(d4);

  if ((num_lanes & 2) != 0) {
    const VFromD<decltype(d2)> v_trailing_lo2 = LoadU(d2, p + leading_len);
    if ((num_lanes & 1) != 0) {
      v_trailing = Combine(
          d4,
          detail::LoadNResizeBitCast(d2, d1, LoadU(d1, p + leading_len + 2)),
          v_trailing_lo2);
    } else {
      v_trailing = detail::LoadNResizeBitCast(d4, d2, v_trailing_lo2);
    }
  } else if ((num_lanes & 1) != 0) {
    v_trailing = detail::LoadNResizeBitCast(d4, d1, LoadU(d1, p + leading_len));
  }

  if (leading_len != 0) {
    if (leading_len >= 8) {
      const VFromD<decltype(d8)> v_hi7 =
          ((leading_len & 4) != 0)
              ? Combine(d8, v_trailing, LoadU(d4, p + 8))
              : detail::LoadNResizeBitCast(d8, d4, v_trailing);
      return Combine(d, v_hi7, LoadU(d8, p));
    } else {
      return detail::LoadNResizeBitCast(d, d8,
                                        Combine(d8, v_trailing, LoadU(d4, p)));
    }
  } else {
    return detail::LoadNResizeBitCast(d, d4, v_trailing);
  }
}

template <class D, HWY_IF_V_SIZE_LE_D(D, 16), HWY_IF_LANES_D(D, 16),
          HWY_IF_NOT_BF16_D(D)>
HWY_API VFromD<D> LoadNOr(VFromD<D> no, D d, const TFromD<D>* HWY_RESTRICT p,
                          size_t num_lanes) {
  const FixedTag<TFromD<D>, 8> d8;
  const Half<decltype(d8)> d4;
  const Half<decltype(d4)> d2;
  const Half<decltype(d2)> d1;

  if (num_lanes >= 16return LoadU(d, p);
  if (num_lanes == 0return no;
  if (num_lanes == 1return InsertLane(no, 0, p[0]);

  const size_t leading_len = num_lanes & 12;
  VFromD<decltype(d4)> v_trailing = ResizeBitCast(d4, no);

  if ((num_lanes & 2) != 0) {
    const VFromD<decltype(d2)> v_trailing_lo2 = LoadU(d2, p + leading_len);
    if ((num_lanes & 1) != 0) {
      v_trailing = Combine(
          d4,
          InterleaveLower(ResizeBitCast(d2, LoadU(d1, p + leading_len + 2)),
                          ResizeBitCast(d2, no)),
          v_trailing_lo2);
    } else {
      v_trailing = ConcatUpperLower(d4, ResizeBitCast(d4, no),
                                    ResizeBitCast(d4, v_trailing_lo2));
    }
  } else if ((num_lanes & 1) != 0) {
    v_trailing = InsertLane(ResizeBitCast(d4, no), 0, p[leading_len]);
  }

  if (leading_len != 0) {
    if (leading_len >= 8) {
      const VFromD<decltype(d8)> v_hi7 =
          ((leading_len & 4) != 0)
              ? Combine(d8, v_trailing, LoadU(d4, p + 8))
              : ConcatUpperLower(d8, ResizeBitCast(d8, no),
                                 ResizeBitCast(d8, v_trailing));
      return Combine(d, v_hi7, LoadU(d8, p));
    } else {
      return ConcatUpperLower(
          d, ResizeBitCast(d, no),
          ResizeBitCast(d, Combine(d8, v_trailing, LoadU(d4, p))));
    }
  } else {
    const Repartition<uint32_t, D> du32;
    // lowest 4 bytes from v_trailing, next 4 from no.
    const VFromD<decltype(du32)> lo8 =
        InterleaveLower(ResizeBitCast(du32, v_trailing), BitCast(du32, no));
    return ConcatUpperLower(d, ResizeBitCast(d, no), ResizeBitCast(d, lo8));
  }
}

#if HWY_MAX_BYTES >= 32

template <class D, HWY_IF_V_SIZE_GT_D(D, 16), HWY_IF_NOT_BF16_D(D)>
HWY_API VFromD<D> LoadN(D d, const TFromD<D>* HWY_RESTRICT p,
                        size_t num_lanes) {
  if (num_lanes >= Lanes(d)) return LoadU(d, p);

  const Half<decltype(d)> dh;
  const size_t half_N = Lanes(dh);
  if (num_lanes <= half_N) {
    return ZeroExtendVector(d, LoadN(dh, p, num_lanes));
  } else {
    const VFromD<decltype(dh)> v_lo = LoadU(dh, p);
    const VFromD<decltype(dh)> v_hi = LoadN(dh, p + half_N, num_lanes - half_N);
    return Combine(d, v_hi, v_lo);
  }
}

template <class D, HWY_IF_V_SIZE_GT_D(D, 16), HWY_IF_NOT_BF16_D(D)>
HWY_API VFromD<D> LoadNOr(VFromD<D> no, D d, const TFromD<D>* HWY_RESTRICT p,
                          size_t num_lanes) {
  if (num_lanes >= Lanes(d)) return LoadU(d, p);

  const Half<decltype(d)> dh;
  const size_t half_N = Lanes(dh);
  const VFromD<decltype(dh)> no_h = LowerHalf(no);
  if (num_lanes <= half_N) {
    return ConcatUpperLower(d, no,
                            ResizeBitCast(d, LoadNOr(no_h, dh, p, num_lanes)));
  } else {
    const VFromD<decltype(dh)> v_lo = LoadU(dh, p);
    const VFromD<decltype(dh)> v_hi =
        LoadNOr(no_h, dh, p + half_N, num_lanes - half_N);
    return Combine(d, v_hi, v_lo);
  }
}

#endif  // HWY_MAX_BYTES >= 32

template <class D, HWY_IF_BF16_D(D)>
HWY_API VFromD<D> LoadN(D d, const TFromD<D>* HWY_RESTRICT p,
                        size_t num_lanes) {
  const RebindToUnsigned<D> du;
  return BitCast(d, LoadN(du, detail::U16LanePointer(p), num_lanes));
}

template <class D, HWY_IF_BF16_D(D)>
HWY_API VFromD<D> LoadNOr(VFromD<D> no, D d, const TFromD<D>* HWY_RESTRICT p,
                          size_t num_lanes) {
  const RebindToUnsigned<D> du;
  return BitCast(
      d, LoadNOr(BitCast(du, no), du, detail::U16LanePointer(p), num_lanes));
}

#else  // !HWY_MEM_OPS_MIGHT_FAULT || HWY_HAVE_SCALABLE

// For SVE and non-sanitizer AVX-512; RVV has its own specialization.
template <class D>
HWY_API VFromD<D> LoadN(D d, const TFromD<D>* HWY_RESTRICT p,
                        size_t num_lanes) {
#if HWY_MEM_OPS_MIGHT_FAULT
  if (num_lanes <= 0return Zero(d);
#endif

  return MaskedLoad(FirstN(d, num_lanes), d, p);
}

template <class D>
HWY_API VFromD<D> LoadNOr(VFromD<D> no, D d, const TFromD<D>* HWY_RESTRICT p,
                          size_t num_lanes) {
#if HWY_MEM_OPS_MIGHT_FAULT
  if (num_lanes <= 0return no;
#endif

  return MaskedLoadOr(no, FirstN(d, num_lanes), d, p);
}

#endif  // HWY_MEM_OPS_MIGHT_FAULT && !HWY_HAVE_SCALABLE
#endif  // HWY_NATIVE_LOAD_N

// ------------------------------ StoreN
#if (defined(HWY_NATIVE_STORE_N) == defined(HWY_TARGET_TOGGLE))
#ifdef HWY_NATIVE_STORE_N
#undef HWY_NATIVE_STORE_N
#else
#define HWY_NATIVE_STORE_N
#endif

#if HWY_MEM_OPS_MIGHT_FAULT && !HWY_HAVE_SCALABLE
namespace detail {

template <class DH, HWY_IF_V_SIZE_LE_D(DH, 4)>
HWY_INLINE VFromD<DH> StoreNGetUpperHalf(DH dh, VFromD<Twice<DH>> v) {
  constexpr size_t kMinShrVectBytes =
      (HWY_TARGET == HWY_NEON || HWY_TARGET == HWY_NEON_WITHOUT_AES) ? 8 : 16;
  const FixedTag<uint8_t, kMinShrVectBytes> d_shift;
  return ResizeBitCast(
      dh, ShiftRightBytes<dh.MaxBytes()>(d_shift, ResizeBitCast(d_shift, v)));
}

template <class DH, HWY_IF_V_SIZE_GT_D(DH, 4)>
HWY_INLINE VFromD<DH> StoreNGetUpperHalf(DH dh, VFromD<Twice<DH>> v) {
  return UpperHalf(dh, v);
}

}  // namespace detail

template <class D, HWY_IF_V_SIZE_LE_D(D, 16), HWY_IF_LANES_D(D, 1),
          typename T = TFromD<D>>
HWY_API void StoreN(VFromD<D> v, D d, T* HWY_RESTRICT p,
                    size_t max_lanes_to_store) {
  if (max_lanes_to_store > 0) {
    StoreU(v, d, p);
  }
}

template <class D, HWY_IF_V_SIZE_LE_D(D, 16), HWY_IF_LANES_D(D, 2),
          typename T = TFromD<D>>
HWY_API void StoreN(VFromD<D> v, D d, T* HWY_RESTRICT p,
                    size_t max_lanes_to_store) {
  if (max_lanes_to_store > 1) {
    StoreU(v, d, p);
  } else if (max_lanes_to_store == 1) {
    const FixedTag<TFromD<D>, 1> d1;
    StoreU(LowerHalf(d1, v), d1, p);
  }
}

template <class D, HWY_IF_V_SIZE_LE_D(D, 16), HWY_IF_LANES_D(D, 4),
          typename T = TFromD<D>>
HWY_API void StoreN(VFromD<D> v, D d, T* HWY_RESTRICT p,
                    size_t max_lanes_to_store) {
  const FixedTag<TFromD<D>, 2> d2;
  const Half<decltype(d2)> d1;

  if (max_lanes_to_store > 1) {
    if (max_lanes_to_store >= 4) {
      StoreU(v, d, p);
    } else {
      StoreU(ResizeBitCast(d2, v), d2, p);
      if (max_lanes_to_store == 3) {
        StoreU(ResizeBitCast(d1, detail::StoreNGetUpperHalf(d2, v)), d1, p + 2);
      }
    }
  } else if (max_lanes_to_store == 1) {
    StoreU(ResizeBitCast(d1, v), d1, p);
  }
}

template <class D, HWY_IF_V_SIZE_LE_D(D, 16), HWY_IF_LANES_D(D, 8),
          typename T = TFromD<D>>
HWY_API void StoreN(VFromD<D> v, D d, T* HWY_RESTRICT p,
                    size_t max_lanes_to_store) {
  const FixedTag<TFromD<D>, 4> d4;
  const Half<decltype(d4)> d2;
  const Half<decltype(d2)> d1;

  if (max_lanes_to_store <= 1) {
    if (max_lanes_to_store == 1) {
      StoreU(ResizeBitCast(d1, v), d1, p);
    }
  } else if (max_lanes_to_store >= 8) {
    StoreU(v, d, p);
  } else if (max_lanes_to_store >= 4) {
    StoreU(LowerHalf(d4, v), d4, p);
    StoreN(detail::StoreNGetUpperHalf(d4, v), d4, p + 4,
           max_lanes_to_store - 4);
  } else {
    StoreN(LowerHalf(d4, v), d4, p, max_lanes_to_store);
  }
}

template <class D, HWY_IF_V_SIZE_LE_D(D, 16), HWY_IF_LANES_D(D, 16),
          typename T = TFromD<D>>
HWY_API void StoreN(VFromD<D> v, D d, T* HWY_RESTRICT p,
                    size_t max_lanes_to_store) {
  const FixedTag<TFromD<D>, 8> d8;
  const Half<decltype(d8)> d4;
  const Half<decltype(d4)> d2;
  const Half<decltype(d2)> d1;

  if (max_lanes_to_store <= 1) {
    if (max_lanes_to_store == 1) {
      StoreU(ResizeBitCast(d1, v), d1, p);
    }
  } else if (max_lanes_to_store >= 16) {
    StoreU(v, d, p);
  } else if (max_lanes_to_store >= 8) {
    StoreU(LowerHalf(d8, v), d8, p);
    StoreN(detail::StoreNGetUpperHalf(d8, v), d8, p + 8,
           max_lanes_to_store - 8);
  } else {
    StoreN(LowerHalf(d8, v), d8, p, max_lanes_to_store);
  }
}

#if HWY_MAX_BYTES >= 32
template <class D, HWY_IF_V_SIZE_GT_D(D, 16), typename T = TFromD<D>>
HWY_API void StoreN(VFromD<D> v, D d, T* HWY_RESTRICT p,
                    size_t max_lanes_to_store) {
  const size_t N = Lanes(d);
  if (max_lanes_to_store >= N) {
    StoreU(v, d, p);
    return;
  }

  const Half<decltype(d)> dh;
  const size_t half_N = Lanes(dh);
  if (max_lanes_to_store <= half_N) {
    StoreN(LowerHalf(dh, v), dh, p, max_lanes_to_store);
  } else {
    StoreU(LowerHalf(dh, v), dh, p);
    StoreN(UpperHalf(dh, v), dh, p + half_N, max_lanes_to_store - half_N);
  }
}
#endif  // HWY_MAX_BYTES >= 32

#else  // !HWY_MEM_OPS_MIGHT_FAULT || HWY_HAVE_SCALABLE
template <class D, typename T = TFromD<D>>
HWY_API void StoreN(VFromD<D> v, D d, T* HWY_RESTRICT p,
                    size_t max_lanes_to_store) {
  const size_t N = Lanes(d);
  const size_t clamped_max_lanes_to_store = HWY_MIN(max_lanes_to_store, N);
#if HWY_MEM_OPS_MIGHT_FAULT
  if (clamped_max_lanes_to_store == 0return;
#endif

  BlendedStore(v, FirstN(d, clamped_max_lanes_to_store), d, p);

  detail::MaybeUnpoison(p, clamped_max_lanes_to_store);
}
#endif  // HWY_MEM_OPS_MIGHT_FAULT && !HWY_HAVE_SCALABLE

#endif  // (defined(HWY_NATIVE_STORE_N) == defined(HWY_TARGET_TOGGLE))

// ------------------------------ Scatter

#if (defined(HWY_NATIVE_SCATTER) == defined(HWY_TARGET_TOGGLE))
#ifdef HWY_NATIVE_SCATTER
#undef HWY_NATIVE_SCATTER
#else
#define HWY_NATIVE_SCATTER
#endif

template <class D, typename T = TFromD<D>>
HWY_API void ScatterOffset(VFromD<D> v, D d, T* HWY_RESTRICT base,
                           VFromD<RebindToSigned<D>> offset) {
  const RebindToSigned<decltype(d)> di;
  using TI = TFromD<decltype(di)>;
  static_assert(sizeof(T) == sizeof(TI), "Index/lane size must match");

  HWY_ALIGN T lanes[MaxLanes(d)];
  Store(v, d, lanes);

  HWY_ALIGN TI offset_lanes[MaxLanes(d)];
  Store(offset, di, offset_lanes);

  uint8_t* base_bytes = reinterpret_cast<uint8_t*>(base);
  for (size_t i = 0; i < MaxLanes(d); ++i) {
    CopyBytes<sizeof(T)>(&lanes[i], base_bytes + offset_lanes[i]);
  }
}

template <class D, typename T = TFromD<D>>
HWY_API void ScatterIndex(VFromD<D> v, D d, T* HWY_RESTRICT base,
                          VFromD<RebindToSigned<D>> index) {
  const RebindToSigned<decltype(d)> di;
  using TI = TFromD<decltype(di)>;
  static_assert(sizeof(T) == sizeof(TI), "Index/lane size must match");

  HWY_ALIGN T lanes[MaxLanes(d)];
  Store(v, d, lanes);

  HWY_ALIGN TI index_lanes[MaxLanes(d)];
  Store(index, di, index_lanes);

  for (size_t i = 0; i < MaxLanes(d); ++i) {
    base[index_lanes[i]] = lanes[i];
  }
}

template <class D, typename T = TFromD<D>>
HWY_API void MaskedScatterIndex(VFromD<D> v, MFromD<D> m, D d,
                                T* HWY_RESTRICT base,
                                VFromD<RebindToSigned<D>> index) {
  const RebindToSigned<decltype(d)> di;
  using TI = TFromD<decltype(di)>;
  static_assert(sizeof(T) == sizeof(TI), "Index/lane size must match");

  HWY_ALIGN T lanes[MaxLanes(d)];
  Store(v, d, lanes);

  HWY_ALIGN TI index_lanes[MaxLanes(d)];
  Store(index, di, index_lanes);

  HWY_ALIGN TI mask_lanes[MaxLanes(di)];
  Store(BitCast(di, VecFromMask(d, m)), di, mask_lanes);

  for (size_t i = 0; i < MaxLanes(d); ++i) {
    if (mask_lanes[i]) base[index_lanes[i]] = lanes[i];
  }
}

#endif  // (defined(HWY_NATIVE_SCATTER) == defined(HWY_TARGET_TOGGLE))

// ------------------------------ Gather

#if (defined(HWY_NATIVE_GATHER) == defined(HWY_TARGET_TOGGLE))
#ifdef HWY_NATIVE_GATHER
#undef HWY_NATIVE_GATHER
#else
#define HWY_NATIVE_GATHER
#endif

template <class D, typename T = TFromD<D>>
HWY_API VFromD<D> GatherOffset(D d, const T* HWY_RESTRICT base,
                               VFromD<RebindToSigned<D>> offset) {
  const RebindToSigned<D> di;
  using TI = TFromD<decltype(di)>;
  static_assert(sizeof(T) == sizeof(TI), "Index/lane size must match");

  HWY_ALIGN TI offset_lanes[MaxLanes(d)];
  Store(offset, di, offset_lanes);

  HWY_ALIGN T lanes[MaxLanes(d)];
  const uint8_t* base_bytes = reinterpret_cast<const uint8_t*>(base);
  for (size_t i = 0; i < MaxLanes(d); ++i) {
    HWY_DASSERT(offset_lanes[i] >= 0);
    CopyBytes<sizeof(T)>(base_bytes + offset_lanes[i], &lanes[i]);
  }
  return Load(d, lanes);
}

template <class D, typename T = TFromD<D>>
HWY_API VFromD<D> GatherIndex(D d, const T* HWY_RESTRICT base,
                              VFromD<RebindToSigned<D>> index) {
  const RebindToSigned<D> di;
  using TI = TFromD<decltype(di)>;
  static_assert(sizeof(T) == sizeof(TI), "Index/lane size must match");

  HWY_ALIGN TI index_lanes[MaxLanes(d)];
  Store(index, di, index_lanes);

  HWY_ALIGN T lanes[MaxLanes(d)];
  for (size_t i = 0; i < MaxLanes(d); ++i) {
    HWY_DASSERT(index_lanes[i] >= 0);
    lanes[i] = base[index_lanes[i]];
  }
  return Load(d, lanes);
}

template <class D, typename T = TFromD<D>>
HWY_API VFromD<D> MaskedGatherIndex(MFromD<D> m, D d,
                                    const T* HWY_RESTRICT base,
                                    VFromD<RebindToSigned<D>> index) {
  const RebindToSigned<D> di;
  using TI = TFromD<decltype(di)>;
  static_assert(sizeof(T) == sizeof(TI), "Index/lane size must match");

  HWY_ALIGN TI index_lanes[MaxLanes(di)];
  Store(index, di, index_lanes);

  HWY_ALIGN TI mask_lanes[MaxLanes(di)];
  Store(BitCast(di, VecFromMask(d, m)), di, mask_lanes);

  HWY_ALIGN T lanes[MaxLanes(d)];
  for (size_t i = 0; i < MaxLanes(d); ++i) {
    HWY_DASSERT(index_lanes[i] >= 0);
    lanes[i] = mask_lanes[i] ? base[index_lanes[i]] : T{0};
  }
  return Load(d, lanes);
}

template <class D, typename T = TFromD<D>>
HWY_API VFromD<D> MaskedGatherIndexOr(VFromD<D> no, MFromD<D> m, D d,
                                      const T* HWY_RESTRICT base,
                                      VFromD<RebindToSigned<D>> index) {
  const RebindToSigned<D> di;
  using TI = TFromD<decltype(di)>;
  static_assert(sizeof(T) == sizeof(TI), "Index/lane size must match");

  HWY_ALIGN TI index_lanes[MaxLanes(di)];
  Store(index, di, index_lanes);

  HWY_ALIGN TI mask_lanes[MaxLanes(di)];
  Store(BitCast(di, VecFromMask(d, m)), di, mask_lanes);

  HWY_ALIGN T no_lanes[MaxLanes(d)];
  Store(no, d, no_lanes);

  HWY_ALIGN T lanes[MaxLanes(d)];
  for (size_t i = 0; i < MaxLanes(d); ++i) {
    HWY_DASSERT(index_lanes[i] >= 0);
    lanes[i] = mask_lanes[i] ? base[index_lanes[i]] : no_lanes[i];
  }
  return Load(d, lanes);
}

#endif  // (defined(HWY_NATIVE_GATHER) == defined(HWY_TARGET_TOGGLE))

// ------------------------------ ScatterN/GatherN

template <class D, typename T = TFromD<D>>
HWY_API void ScatterIndexN(VFromD<D> v, D d, T* HWY_RESTRICT base,
                           VFromD<RebindToSigned<D>> index,
                           const size_t max_lanes_to_store) {
  MaskedScatterIndex(v, FirstN(d, max_lanes_to_store), d, base, index);
}

template <class D, typename T = TFromD<D>>
HWY_API VFromD<D> GatherIndexN(D d, const T* HWY_RESTRICT base,
                               VFromD<RebindToSigned<D>> index,
                               const size_t max_lanes_to_load) {
  return MaskedGatherIndex(FirstN(d, max_lanes_to_load), d, base, index);
}

// ------------------------------ Integer AbsDiff and SumsOf8AbsDiff

#if (defined(HWY_NATIVE_INTEGER_ABS_DIFF) == defined(HWY_TARGET_TOGGLE))
#ifdef HWY_NATIVE_INTEGER_ABS_DIFF
#undef HWY_NATIVE_INTEGER_ABS_DIFF
#else
#define HWY_NATIVE_INTEGER_ABS_DIFF
#endif

template <class V, HWY_IF_NOT_FLOAT_NOR_SPECIAL_V(V)>
HWY_API V AbsDiff(V a, V b) {
  return Sub(Max(a, b), Min(a, b));
}

#endif  // HWY_NATIVE_INTEGER_ABS_DIFF

#if (defined(HWY_NATIVE_SUMS_OF_8_ABS_DIFF) == defined(HWY_TARGET_TOGGLE))
#ifdef HWY_NATIVE_SUMS_OF_8_ABS_DIFF
#undef HWY_NATIVE_SUMS_OF_8_ABS_DIFF
#else
#define HWY_NATIVE_SUMS_OF_8_ABS_DIFF
#endif

template <class V, HWY_IF_UI8_D(DFromV<V>),
          HWY_IF_V_SIZE_GT_D(DFromV<V>, (HWY_TARGET == HWY_SCALAR ? 0 : 4))>
HWY_API Vec<RepartitionToWideX3<DFromV<V>>> SumsOf8AbsDiff(V a, V b) {
  const DFromV<decltype(a)> d;
  const RebindToUnsigned<decltype(d)> du;
  const RepartitionToWideX3<decltype(d)> dw;

  return BitCast(dw, SumsOf8(BitCast(du, AbsDiff(a, b))));
}

#endif  // HWY_NATIVE_SUMS_OF_8_ABS_DIFF

// ------------------------------ SaturatedAdd/SaturatedSub for UI32/UI64

#if (defined(HWY_NATIVE_I32_SATURATED_ADDSUB) == defined(HWY_TARGET_TOGGLE))
#ifdef HWY_NATIVE_I32_SATURATED_ADDSUB
#undef HWY_NATIVE_I32_SATURATED_ADDSUB
#else
#define HWY_NATIVE_I32_SATURATED_ADDSUB
#endif

template <class V, HWY_IF_I32_D(DFromV<V>)>
HWY_API V SaturatedAdd(V a, V b) {
  const DFromV<decltype(a)> d;
  const auto sum = Add(a, b);
  const auto overflow_mask = AndNot(Xor(a, b), Xor(a, sum));
  const auto overflow_result =
      Xor(BroadcastSignBit(a), Set(d, LimitsMax<int32_t>()));
  return IfNegativeThenElse(overflow_mask, overflow_result, sum);
}

template <class V, HWY_IF_I32_D(DFromV<V>)>
HWY_API V SaturatedSub(V a, V b) {
  const DFromV<decltype(a)> d;
  const auto diff = Sub(a, b);
  const auto overflow_mask = And(Xor(a, b), Xor(a, diff));
  const auto overflow_result =
      Xor(BroadcastSignBit(a), Set(d, LimitsMax<int32_t>()));
  return IfNegativeThenElse(overflow_mask, overflow_result, diff);
}

#endif  // HWY_NATIVE_I32_SATURATED_ADDSUB

#if (defined(HWY_NATIVE_I64_SATURATED_ADDSUB) == defined(HWY_TARGET_TOGGLE))
#ifdef HWY_NATIVE_I64_SATURATED_ADDSUB
#undef HWY_NATIVE_I64_SATURATED_ADDSUB
#else
#define HWY_NATIVE_I64_SATURATED_ADDSUB
#endif

template <class V, HWY_IF_I64_D(DFromV<V>)>
HWY_API V SaturatedAdd(V a, V b) {
  const DFromV<decltype(a)> d;
  const auto sum = Add(a, b);
  const auto overflow_mask = AndNot(Xor(a, b), Xor(a, sum));
  const auto overflow_result =
      Xor(BroadcastSignBit(a), Set(d, LimitsMax<int64_t>()));
  return IfNegativeThenElse(overflow_mask, overflow_result, sum);
}

template <class V, HWY_IF_I64_D(DFromV<V>)>
HWY_API V SaturatedSub(V a, V b) {
  const DFromV<decltype(a)> d;
  const auto diff = Sub(a, b);
  const auto overflow_mask = And(Xor(a, b), Xor(a, diff));
  const auto overflow_result =
      Xor(BroadcastSignBit(a), Set(d, LimitsMax<int64_t>()));
  return IfNegativeThenElse(overflow_mask, overflow_result, diff);
}

#endif  // HWY_NATIVE_I64_SATURATED_ADDSUB

#if (defined(HWY_NATIVE_U32_SATURATED_ADDSUB) == defined(HWY_TARGET_TOGGLE))
#ifdef HWY_NATIVE_U32_SATURATED_ADDSUB
#undef HWY_NATIVE_U32_SATURATED_ADDSUB
#else
#define HWY_NATIVE_U32_SATURATED_ADDSUB
#endif

template <class V, HWY_IF_U32_D(DFromV<V>)>
HWY_API V SaturatedAdd(V a, V b) {
  return Add(a, Min(b, Not(a)));
}

template <class V, HWY_IF_U32_D(DFromV<V>)>
HWY_API V SaturatedSub(V a, V b) {
  return Sub(a, Min(a, b));
}

#endif  // HWY_NATIVE_U32_SATURATED_ADDSUB

#if (defined(HWY_NATIVE_U64_SATURATED_ADDSUB) == defined(HWY_TARGET_TOGGLE))
#ifdef HWY_NATIVE_U64_SATURATED_ADDSUB
#undef HWY_NATIVE_U64_SATURATED_ADDSUB
#else
#define HWY_NATIVE_U64_SATURATED_ADDSUB
#endif

template <class V, HWY_IF_U64_D(DFromV<V>)>
HWY_API V SaturatedAdd(V a, V b) {
  return Add(a, Min(b, Not(a)));
}

template <class V, HWY_IF_U64_D(DFromV<V>)>
HWY_API V SaturatedSub(V a, V b) {
  return Sub(a, Min(a, b));
}

#endif  // HWY_NATIVE_U64_SATURATED_ADDSUB

// ------------------------------ Unsigned to signed demotions

template <class DN, HWY_IF_SIGNED_D(DN), class V, HWY_IF_UNSIGNED_V(V),
          class V2 = VFromD<Rebind<TFromV<V>, DN>>,
          hwy::EnableIf<(sizeof(TFromD<DN>) < sizeof(TFromV<V>))>* = nullptr,
          HWY_IF_LANES_D(DFromV<V>, HWY_MAX_LANES_D(DFromV<V2>))>
HWY_API VFromD<DN> DemoteTo(DN dn, V v) {
  const DFromV<decltype(v)> d;
  const RebindToSigned<decltype(d)> di;
  const RebindToUnsigned<decltype(dn)> dn_u;

  // First, do a signed to signed demotion. This will convert any values
  // that are greater than hwy::HighestValue<MakeSigned<TFromV<V>>>() to a
  // negative value.
  const auto i2i_demote_result = DemoteTo(dn, BitCast(di, v));

  // Second, convert any negative values to hwy::HighestValue<TFromD<DN>>()
  // using an unsigned Min operation.
  const auto max_signed_val = Set(dn, hwy::HighestValue<TFromD<DN>>());

  return BitCast(
      dn, Min(BitCast(dn_u, i2i_demote_result), BitCast(dn_u, max_signed_val)));
}

#if HWY_TARGET != HWY_SCALAR || HWY_IDE
template <class DN, HWY_IF_SIGNED_D(DN), class V, HWY_IF_UNSIGNED_V(V),
          class V2 = VFromD<Repartition<TFromV<V>, DN>>,
          HWY_IF_T_SIZE_V(V, sizeof(TFromD<DN>) * 2),
          HWY_IF_LANES_D(DFromV<V>, HWY_MAX_LANES_D(DFromV<V2>))>
HWY_API VFromD<DN> ReorderDemote2To(DN dn, V a, V b) {
  const DFromV<decltype(a)> d;
  const RebindToSigned<decltype(d)> di;
  const RebindToUnsigned<decltype(dn)> dn_u;

  // First, do a signed to signed demotion. This will convert any values
  // that are greater than hwy::HighestValue<MakeSigned<TFromV<V>>>() to a
  // negative value.
  const auto i2i_demote_result =
      ReorderDemote2To(dn, BitCast(di, a), BitCast(di, b));

  // Second, convert any negative values to hwy::HighestValue<TFromD<DN>>()
  // using an unsigned Min operation.
  const auto max_signed_val = Set(dn, hwy::HighestValue<TFromD<DN>>());

  return BitCast(
      dn, Min(BitCast(dn_u, i2i_demote_result), BitCast(dn_u, max_signed_val)));
}
#endif

// ------------------------------ PromoteLowerTo

// There is no codegen advantage for a native version of this. It is provided
// only for convenience.
template <class D, class V>
HWY_API VFromD<D> PromoteLowerTo(D d, V v) {
  // Lanes(d) may differ from Lanes(DFromV<V>()). Use the lane type from V
  // because it cannot be deduced from D (could be either bf16 or f16).
  const Rebind<TFromV<V>, decltype(d)> dh;
  return PromoteTo(d, LowerHalf(dh, v));
}

// ------------------------------ PromoteUpperTo

#if (defined(HWY_NATIVE_PROMOTE_UPPER_TO) == defined(HWY_TARGET_TOGGLE))
#ifdef HWY_NATIVE_PROMOTE_UPPER_TO
#undef HWY_NATIVE_PROMOTE_UPPER_TO
#else
#define HWY_NATIVE_PROMOTE_UPPER_TO
#endif

// This requires UpperHalf.
#if HWY_TARGET != HWY_SCALAR || HWY_IDE

template <class D, class V>
HWY_API VFromD<D> PromoteUpperTo(D d, V v) {
  // Lanes(d) may differ from Lanes(DFromV<V>()). Use the lane type from V
  // because it cannot be deduced from D (could be either bf16 or f16).
  const Rebind<TFromV<V>, decltype(d)> dh;
  return PromoteTo(d, UpperHalf(dh, v));
}

#endif  // HWY_TARGET != HWY_SCALAR
#endif  // HWY_NATIVE_PROMOTE_UPPER_TO

// ------------------------------ PromoteEvenTo/PromoteOddTo

#if HWY_TARGET != HWY_SCALAR
namespace detail {

// Tag dispatch is used in detail::PromoteEvenTo and detail::PromoteOddTo as
// there are target-specific specializations for some of the
// detail::PromoteEvenTo and detail::PromoteOddTo cases on
// SVE/PPC/SSE2/SSSE3/SSE4/AVX2.

// All targets except HWY_SCALAR use the implementations of
// detail::PromoteEvenTo and detail::PromoteOddTo in generic_ops-inl.h for at
// least some of the PromoteEvenTo and PromoteOddTo cases.

// Signed to signed PromoteEvenTo/PromoteOddTo
template <size_t kToLaneSize, class D, class V>
HWY_INLINE VFromD<D> PromoteEvenTo(
    hwy::SignedTag /*to_type_tag*/,
    hwy::SizeTag<kToLaneSize> /*to_lane_size_tag*/,
    hwy::SignedTag /*from_type_tag*/, D d_to, V v) {
#if HWY_IS_LITTLE_ENDIAN
  // On little-endian targets, need to shift each lane of the bitcasted vector
  // left by kToLaneSize * 4 bits to get the bits of the even source lanes into
  // the upper kToLaneSize * 4 bits of even_in_hi.
  const auto even_in_hi = ShiftLeft<kToLaneSize * 4>(BitCast(d_to, v));
#else
  // On big-endian targets, the bits of the even source lanes are already in
  // the upper kToLaneSize * 4 bits of the lanes of the bitcasted vector.
  const auto even_in_hi = BitCast(d_to, v);
#endif

  // Right-shift even_in_hi by kToLaneSize * 4 bits
  return ShiftRight<kToLaneSize * 4>(even_in_hi);
}

template <size_t kToLaneSize, class D, class V>
HWY_INLINE VFromD<D> PromoteOddTo(
    hwy::SignedTag /*to_type_tag*/,
    hwy::SizeTag<kToLaneSize> /*to_lane_size_tag*/,
    hwy::SignedTag /*from_type_tag*/, D d_to, V v) {
#if HWY_IS_LITTLE_ENDIAN
  // On little-endian targets, the bits of the odd source lanes are already in
  // the upper kToLaneSize * 4 bits of the lanes of the bitcasted vector.
  const auto odd_in_hi = BitCast(d_to, v);
#else
  // On big-endian targets, need to shift each lane of the bitcasted vector left
  // by kToLaneSize * 4 bits to get the bits of the odd source lanes into the
  // upper kToLaneSize * 4 bits of odd_in_hi.
  const auto odd_in_hi = ShiftLeft<kToLaneSize * 4>(BitCast(d_to, v));
#endif

  // Right-shift odd_in_hi by kToLaneSize * 4 bits
  return ShiftRight<kToLaneSize * 4>(odd_in_hi);
}

// Unsigned to unsigned PromoteEvenTo/PromoteOddTo
template <size_t kToLaneSize, class D, class V>
HWY_INLINE VFromD<D> PromoteEvenTo(
    hwy::UnsignedTag /*to_type_tag*/,
    hwy::SizeTag<kToLaneSize> /*to_lane_size_tag*/,
    hwy::UnsignedTag /*from_type_tag*/, D d_to, V v) {
#if HWY_IS_LITTLE_ENDIAN
  // On little-endian targets, the bits of the even source lanes are already
  // in the lower kToLaneSize * 4 bits of the lanes of the bitcasted vector.

  // Simply need to zero out the upper bits of each lane of the bitcasted
  // vector.
  return And(BitCast(d_to, v),
             Set(d_to, static_cast<TFromD<D>>(LimitsMax<TFromV<V>>())));
#else
  // On big-endian targets, need to shift each lane of the bitcasted vector
  // right by kToLaneSize * 4 bits to get the bits of the even source lanes into
  // the lower kToLaneSize * 4 bits of the result.

  // The right shift below will zero out the upper kToLaneSize * 4 bits of the
  // result.
  return ShiftRight<kToLaneSize * 4>(BitCast(d_to, v));
#endif
}

template <size_t kToLaneSize, class D, class V>
HWY_INLINE VFromD<D> PromoteOddTo(
    hwy::UnsignedTag /*to_type_tag*/,
    hwy::SizeTag<kToLaneSize> /*to_lane_size_tag*/,
    hwy::UnsignedTag /*from_type_tag*/, D d_to, V v) {
#if HWY_IS_LITTLE_ENDIAN
  // On little-endian targets, need to shift each lane of the bitcasted vector
  // right by kToLaneSize * 4 bits to get the bits of the odd source lanes into
  // the lower kToLaneSize * 4 bits of the result.

  // The right shift below will zero out the upper kToLaneSize * 4 bits of the
  // result.
  return ShiftRight<kToLaneSize * 4>(BitCast(d_to, v));
#else
  // On big-endian targets, the bits of the even source lanes are already
  // in the lower kToLaneSize * 4 bits of the lanes of the bitcasted vector.

  // Simply need to zero out the upper bits of each lane of the bitcasted
  // vector.
  return And(BitCast(d_to, v),
             Set(d_to, static_cast<TFromD<D>>(LimitsMax<TFromV<V>>())));
#endif
}

// Unsigned to signed: Same as unsigned->unsigned PromoteEvenTo/PromoteOddTo
// followed by BitCast to signed
template <size_t kToLaneSize, class D, class V>
HWY_INLINE VFromD<D> PromoteEvenTo(
    hwy::SignedTag /*to_type_tag*/,
    hwy::SizeTag<kToLaneSize> /*to_lane_size_tag*/,
    hwy::UnsignedTag /*from_type_tag*/, D d_to, V v) {
  const RebindToUnsigned<decltype(d_to)> du_to;
  return BitCast(d_to,
                 PromoteEvenTo(hwy::UnsignedTag(), hwy::SizeTag<kToLaneSize>(),
                               hwy::UnsignedTag(), du_to, v));
}

template <size_t kToLaneSize, class D, class V>
HWY_INLINE VFromD<D> PromoteOddTo(
    hwy::SignedTag /*to_type_tag*/,
    hwy::SizeTag<kToLaneSize> /*to_lane_size_tag*/,
    hwy::UnsignedTag /*from_type_tag*/, D d_to, V v) {
  const RebindToUnsigned<decltype(d_to)> du_to;
  return BitCast(d_to,
                 PromoteOddTo(hwy::UnsignedTag(), hwy::SizeTag<kToLaneSize>(),
                              hwy::UnsignedTag(), du_to, v));
}

// BF16->F32 PromoteEvenTo

// NOTE: It is possible for FromTypeTag to be hwy::SignedTag or hwy::UnsignedTag
// instead of hwy::FloatTag on targets that use scalable vectors.

// VBF16 is considered to be a bfloat16_t vector if TFromV<VBF16> is the same
// type as TFromV<VFromD<Repartition<bfloat16_t, DF32>>>

// The BF16->F32 PromoteEvenTo overload is only enabled if VBF16 is considered
// to be a bfloat16_t vector.
template <class FromTypeTag, class DF32, class VBF16,
          class VBF16_2 = VFromD<Repartition<bfloat16_t, DF32>>,
          hwy::EnableIf<IsSame<TFromV<VBF16>, TFromV<VBF16_2>>()>* = nullptr>
HWY_INLINE VFromD<DF32> PromoteEvenTo(hwy::FloatTag /*to_type_tag*/,
                                      hwy::SizeTag<4/*to_lane_size_tag*/,
                                      FromTypeTag /*from_type_tag*/, DF32 d_to,
                                      VBF16 v) {
  const RebindToUnsigned<decltype(d_to)> du_to;
#if HWY_IS_LITTLE_ENDIAN
  // On little-endian platforms, need to shift left each lane of the bitcasted
  // vector by 16 bits.
  return BitCast(d_to, ShiftLeft<16>(BitCast(du_to, v)));
#else
  // On big-endian platforms, the even lanes of the source vector are already
  // in the upper 16 bits of the lanes of the bitcasted vector.

  // Need to simply zero out the lower 16 bits of each lane of the bitcasted
  // vector.
  return BitCast(d_to,
                 And(BitCast(du_to, v), Set(du_to, uint32_t{0xFFFF0000u})));
#endif
}

// BF16->F32 PromoteOddTo

// NOTE: It is possible for FromTypeTag to be hwy::SignedTag or hwy::UnsignedTag
// instead of hwy::FloatTag on targets that use scalable vectors.

// VBF16 is considered to be a bfloat16_t vector if TFromV<VBF16> is the same
// type as TFromV<VFromD<Repartition<bfloat16_t, DF32>>>

// The BF16->F32 PromoteEvenTo overload is only enabled if VBF16 is considered
// to be a bfloat16_t vector.
template <class FromTypeTag, class DF32, class VBF16,
          class VBF16_2 = VFromD<Repartition<bfloat16_t, DF32>>,
          hwy::EnableIf<IsSame<TFromV<VBF16>, TFromV<VBF16_2>>()>* = nullptr>
HWY_INLINE VFromD<DF32> PromoteOddTo(hwy::FloatTag /*to_type_tag*/,
                                     hwy::SizeTag<4/*to_lane_size_tag*/,
                                     FromTypeTag /*from_type_tag*/, DF32 d_to,
                                     VBF16 v) {
  const RebindToUnsigned<decltype(d_to)> du_to;
#if HWY_IS_LITTLE_ENDIAN
  // On little-endian platforms, the odd lanes of the source vector are already
  // in the upper 16 bits of the lanes of the bitcasted vector.

  // Need to simply zero out the lower 16 bits of each lane of the bitcasted
  // vector.
  return BitCast(d_to,
                 And(BitCast(du_to, v), Set(du_to, uint32_t{0xFFFF0000u})));
#else
  // On big-endian platforms, need to shift left each lane of the bitcasted
  // vector by 16 bits.
  return BitCast(d_to, ShiftLeft<16>(BitCast(du_to, v)));
#endif
}

// Default PromoteEvenTo/PromoteOddTo implementations
template <class ToTypeTag, size_t kToLaneSize, class FromTypeTag, class D,
          class V, HWY_IF_LANES_D(D, 1)>
HWY_INLINE VFromD<D> PromoteEvenTo(
    ToTypeTag /*to_type_tag*/, hwy::SizeTag<kToLaneSize> /*to_lane_size_tag*/,
    FromTypeTag /*from_type_tag*/, D d_to, V v) {
  return PromoteLowerTo(d_to, v);
}

template <class ToTypeTag, size_t kToLaneSize, class FromTypeTag, class D,
          class V, HWY_IF_LANES_GT_D(D, 1)>
HWY_INLINE VFromD<D> PromoteEvenTo(
    ToTypeTag /*to_type_tag*/, hwy::SizeTag<kToLaneSize> /*to_lane_size_tag*/,
    FromTypeTag /*from_type_tag*/, D d_to, V v) {
  const DFromV<decltype(v)> d;
  return PromoteLowerTo(d_to, ConcatEven(d, v, v));
}

template <class ToTypeTag, size_t kToLaneSize, class FromTypeTag, class D,
          class V>
HWY_INLINE VFromD<D> PromoteOddTo(
    ToTypeTag /*to_type_tag*/, hwy::SizeTag<kToLaneSize> /*to_lane_size_tag*/,
    FromTypeTag /*from_type_tag*/, D d_to, V v) {
  const DFromV<decltype(v)> d;
  return PromoteLowerTo(d_to, ConcatOdd(d, v, v));
}

}  // namespace detail

template <class D, class V, HWY_IF_T_SIZE_D(D, 2 * sizeof(TFromV<V>)),
          class V2 = VFromD<Repartition<TFromV<V>, D>>,
          HWY_IF_LANES_D(DFromV<V>, HWY_MAX_LANES_V(V2))>
HWY_API VFromD<D> PromoteEvenTo(D d, V v) {
  return detail::PromoteEvenTo(hwy::TypeTag<TFromD<D>>(),
                               hwy::SizeTag<sizeof(TFromD<D>)>(),
                               hwy::TypeTag<TFromV<V>>(), d, v);
}

template <class D, class V, HWY_IF_T_SIZE_D(D, 2 * sizeof(TFromV<V>)),
          class V2 = VFromD<Repartition<TFromV<V>, D>>,
          HWY_IF_LANES_D(DFromV<V>, HWY_MAX_LANES_V(V2))>
HWY_API VFromD<D> PromoteOddTo(D d, V v) {
  return detail::PromoteOddTo(hwy::TypeTag<TFromD<D>>(),
                              hwy::SizeTag<sizeof(TFromD<D>)>(),
                              hwy::TypeTag<TFromV<V>>(), d, v);
}
#endif  // HWY_TARGET != HWY_SCALAR

// ------------------------------ float16_t <-> float

#if (defined(HWY_NATIVE_F16C) == defined(HWY_TARGET_TOGGLE))
#ifdef HWY_NATIVE_F16C
#undef HWY_NATIVE_F16C
#else
#define HWY_NATIVE_F16C
#endif

template <class D, HWY_IF_F32_D(D)>
HWY_API VFromD<D> PromoteTo(D df32, VFromD<Rebind<float16_t, D>> v) {
  const RebindToSigned<decltype(df32)> di32;
  const RebindToUnsigned<decltype(df32)> du32;
  const Rebind<uint16_t, decltype(df32)> du16;
  using VU32 = VFromD<decltype(du32)>;

  const VU32 bits16 = PromoteTo(du32, BitCast(du16, v));
  const VU32 sign = ShiftRight<15>(bits16);
  const VU32 biased_exp = And(ShiftRight<10>(bits16), Set(du32, 0x1F));
  const VU32 mantissa = And(bits16, Set(du32, 0x3FF));
  const VU32 subnormal =
      BitCast(du32, Mul(ConvertTo(df32, BitCast(di32, mantissa)),
                        Set(df32, 1.0f / 16384 / 1024)));

  const VU32 biased_exp32 = Add(biased_exp, Set(du32, 127 - 15));
  const VU32 mantissa32 = ShiftLeft<23 - 10>(mantissa);
  const VU32 normal = Or(ShiftLeft<23>(biased_exp32), mantissa32);
  const VU32 bits32 = IfThenElse(Eq(biased_exp, Zero(du32)), subnormal, normal);
  return BitCast(df32, Or(ShiftLeft<31>(sign), bits32));
}

template <class D, HWY_IF_F16_D(D)>
HWY_API VFromD<D> DemoteTo(D df16, VFromD<Rebind<float, D>> v) {
  const RebindToSigned<decltype(df16)> di16;
  const Rebind<int32_t, decltype(df16)> di32;
  const RebindToFloat<decltype(di32)> df32;
  const RebindToUnsigned<decltype(df32)> du32;

  // There are 23 fractional bits (plus the implied 1 bit) in the mantissa of
  // a F32, and there are 10 fractional bits (plus the implied 1 bit) in the
  // mantissa of a F16

  // We want the unbiased exponent of round_incr[i] to be at least (-14) + 13 as
  // 2^(-14) is the smallest positive normal F16 value and as we want 13
  // mantissa bits (including the implicit 1 bit) to the left of the
  // F32 mantissa bits in rounded_val[i] since 23 - 10 is equal to 13

  // The biased exponent of round_incr[i] needs to be at least 126 as
  // (-14) + 13 + 127 is equal to 126

  // We also want to biased exponent of round_incr[i] to be less than or equal
  // to 255 (which is equal to MaxExponentField<float>())

  // The biased F64 exponent of round_incr is equal to
  // HWY_MAX(HWY_MIN(((exp_bits[i] >> 23) & 255) + 13, 255), 126)

  // hi9_bits[i] is equal to the upper 9 bits of v[i]
  const auto hi9_bits = ShiftRight<23>(BitCast(du32, v));

  const auto k13 = Set(du32, uint32_t{13u});

  // Minimum biased F32 exponent of round_incr
  const auto k126 = Set(du32, uint32_t{126u});

  // round_incr_hi9_bits[i] is equivalent to
  // (hi9_bits[i] & 0x100) |
  // HWY_MAX(HWY_MIN((hi9_bits[i] & 0xFF) + 13, 255), 126)

#if HWY_TARGET == HWY_SCALAR || HWY_TARGET == HWY_EMU128
  const auto k255 = Set(du32, uint32_t{255u});
  const auto round_incr_hi9_bits = BitwiseIfThenElse(
      k255, Max(Min(Add(And(hi9_bits, k255), k13), k255), k126), hi9_bits);
#else
  // On targets other than SCALAR and EMU128, the exponent bits of hi9_bits can
  // be incremented by 13 and clamped to the [13, 255] range without overflowing
  // into the sign bit of hi9_bits by using U8 SaturatedAdd as there are 8
  // exponent bits in an F32

  // U8 Max can be used on targets other than SCALAR and EMU128 to clamp
  // ((hi9_bits & 0xFF) + 13) to the [126, 255] range without affecting the sign
  // bit

  const Repartition<uint8_t, decltype(du32)> du32_as_u8;
  const auto round_incr_hi9_bits = BitCast(
      du32,
      Max(SaturatedAdd(BitCast(du32_as_u8, hi9_bits), BitCast(du32_as_u8, k13)),
          BitCast(du32_as_u8, k126)));
#endif

  // (round_incr_hi9_bits >> 8) is equal to (hi9_bits >> 8), and
  // (round_incr_hi9_bits & 0xFF) is equal to
  // HWY_MAX(HWY_MIN((round_incr_hi9_bits & 0xFF) + 13, 255), 126)

  const auto round_incr = BitCast(df32, ShiftLeft<23>(round_incr_hi9_bits));

  // Add round_incr[i] to v[i] to round the mantissa to the nearest F16 mantissa
  // and to move the fractional bits of the resulting non-NaN mantissa down to
  // the lower 10 bits of rounded_val if (v[i] + round_incr[i]) is a non-NaN
  // value
  const auto rounded_val = Add(v, round_incr);

  // rounded_val_bits is the bits of rounded_val as a U32
  const auto rounded_val_bits = BitCast(du32, rounded_val);

  // rounded_val[i] is known to have the same biased exponent as round_incr[i]
  // as |round_incr[i]| > 2^12*|v[i]| is true if round_incr[i] is a finite
  // value, round_incr[i] and v[i] both have the same sign, and |round_incr[i]|
  // is either a power of 2 that is greater than or equal to 2^-1 or infinity.

  // If rounded_val[i] is a finite F32 value, then
  // (rounded_val_bits[i] & 0x00000FFF) is the bit representation of the
  // rounded mantissa of rounded_val[i] as a UQ2.10 fixed point number that is
  // in the range [0, 2].

  // In other words, (rounded_val_bits[i] & 0x00000FFF) is between 0 and 0x0800,
  // with (rounded_val_bits[i] & 0x000003FF) being the fractional bits of the
  // resulting F16 mantissa, if rounded_v[i] is a finite F32 value.

  // (rounded_val_bits[i] & 0x007FF000) == 0 is guaranteed to be true if
  // rounded_val[i] is a non-NaN value

  // The biased exponent of rounded_val[i] is guaranteed to be at least 126 as
  // the biased exponent of round_incr[i] is at least 126 and as both v[i] and
  // round_incr[i] have the same sign bit

  // The ULP of a F32 value with a biased exponent of 126 is equal to
  // 2^(126 - 127 - 23), which is equal to 2^(-24) (which is also the ULP of a
  // F16 value with a biased exponent of 0 or 1 as (1 - 15 - 10) is equal to
  // -24)

  // The biased exponent (before subtracting by 126) needs to be clamped to the
  // [126, 157] range as 126 + 31 is equal to 157 and as 31 is the largest
  // biased exponent of a F16.

  // The biased exponent of the resulting F16 value is equal to
  // HWY_MIN((round_incr_hi9_bits[i] & 0xFF) +
  //         ((rounded_val_bits[i] >> 10) & 0xFF), 157) - 126

#if HWY_TARGET == HWY_SCALAR || HWY_TARGET == HWY_EMU128
  auto f16_exp_bits =
      Min(Add(ShiftLeft<10>(And(round_incr_hi9_bits, k255)),
              And(rounded_val_bits,
                  Set(du32, static_cast<uint32_t>(uint32_t{0xFFu} << 10)))),
          Set(du32, static_cast<uint32_t>(uint32_t{157u} << 10)));
#else
  auto f16_exp_bits = ShiftLeft<10>(BitCast(
      du32,
      Min(SaturatedAdd(BitCast(du32_as_u8, round_incr_hi9_bits),
                       BitCast(du32_as_u8, ShiftRight<10>(rounded_val_bits))),
          BitCast(du32_as_u8, Set(du32, uint32_t{157})))));
#endif

  f16_exp_bits =
      Sub(f16_exp_bits, Set(du32, static_cast<uint32_t>(uint32_t{126u} << 10)));

  const auto f16_unmasked_mant_bits =
      BitCast(di32, Or(rounded_val, VecFromMask(df32, IsNaN(rounded_val))));

  const auto f16_exp_mant_bits =
      OrAnd(BitCast(di32, f16_exp_bits), f16_unmasked_mant_bits,
            Set(di32, int32_t{0x03FF}));

  // f16_bits_as_i32 is the F16 bits sign-extended to an I32 (with the upper 17
  // bits of f16_bits_as_i32[i] set to the sign bit of rounded_val[i]) to allow
  // efficient truncation of the F16 bits to an I16 using an I32->I16 DemoteTo
  // operation
  const auto f16_bits_as_i32 =
      OrAnd(f16_exp_mant_bits, ShiftRight<16>(BitCast(di32, rounded_val_bits)),
            Set(di32, static_cast<int32_t>(0xFFFF8000u)));
  return BitCast(df16, DemoteTo(di16, f16_bits_as_i32));
}

#endif  // HWY_NATIVE_F16C

// ------------------------------ F64->F16 DemoteTo
#if (defined(HWY_NATIVE_DEMOTE_F64_TO_F16) == defined(HWY_TARGET_TOGGLE))
#ifdef HWY_NATIVE_DEMOTE_F64_TO_F16
#undef HWY_NATIVE_DEMOTE_F64_TO_F16
#else
#define HWY_NATIVE_DEMOTE_F64_TO_F16
#endif

#if HWY_HAVE_FLOAT64
template <class D, HWY_IF_F16_D(D)>
HWY_API VFromD<D> DemoteTo(D df16, VFromD<Rebind<double, D>> v) {
  const Rebind<double, D> df64;
  const Rebind<uint64_t, D> du64;
  const Rebind<float, D> df32;

  // The mantissa bits of v[i] are first rounded using round-to-odd rounding to
  // the nearest F64 value that has the lower 29 bits zeroed out to ensure that
  // the result is correctly rounded to a F16.

  const auto vf64_rounded = OrAnd(
      And(v,
          BitCast(df64, Set(du64, static_cast<uint64_t>(0xFFFFFFFFE0000000u)))),
      BitCast(df64, Add(BitCast(du64, v),
                        Set(du64, static_cast<uint64_t>(0x000000001FFFFFFFu)))),
      BitCast(df64, Set(du64, static_cast<uint64_t>(0x0000000020000000ULL))));

  return DemoteTo(df16, DemoteTo(df32, vf64_rounded));
}
#endif  // HWY_HAVE_FLOAT64

#endif  // HWY_NATIVE_DEMOTE_F64_TO_F16

// ------------------------------ F16->F64 PromoteTo
#if (defined(HWY_NATIVE_PROMOTE_F16_TO_F64) == defined(HWY_TARGET_TOGGLE))
#ifdef HWY_NATIVE_PROMOTE_F16_TO_F64
#undef HWY_NATIVE_PROMOTE_F16_TO_F64
#else
#define HWY_NATIVE_PROMOTE_F16_TO_F64
#endif

#if HWY_HAVE_FLOAT64
template <class D, HWY_IF_F64_D(D)>
HWY_API VFromD<D> PromoteTo(D df64, VFromD<Rebind<float16_t, D>> v) {
  return PromoteTo(df64, PromoteTo(Rebind<float, D>(), v));
}
#endif  // HWY_HAVE_FLOAT64

#endif  // HWY_NATIVE_PROMOTE_F16_TO_F64

// ------------------------------ SumsOf2

#if HWY_TARGET != HWY_SCALAR
namespace detail {

template <class TypeTag, size_t kLaneSize, class V>
HWY_INLINE VFromD<RepartitionToWide<DFromV<V>>> SumsOf2(
    TypeTag /*type_tag*/, hwy::SizeTag<kLaneSize> /*lane_size_tag*/, V v) {
  const DFromV<decltype(v)> d;
  const RepartitionToWide<decltype(d)> dw;
  return Add(PromoteEvenTo(dw, v), PromoteOddTo(dw, v));
}

}  // namespace detail

template <class V>
HWY_API VFromD<RepartitionToWide<DFromV<V>>> SumsOf2(V v) {
  return detail::SumsOf2(hwy::TypeTag<TFromV<V>>(),
                         hwy::SizeTag<sizeof(TFromV<V>)>(), v);
}
#endif  // HWY_TARGET != HWY_SCALAR

// ------------------------------ SumsOf4

namespace detail {

template <class TypeTag, size_t kLaneSize, class V>
HWY_INLINE VFromD<RepartitionToWideX2<DFromV<V>>> SumsOf4(
    TypeTag /*type_tag*/, hwy::SizeTag<kLaneSize> /*lane_size_tag*/, V v) {
  using hwy::HWY_NAMESPACE::SumsOf2;
  return SumsOf2(SumsOf2(v));
}

}  // namespace detail

template <class V>
HWY_API VFromD<RepartitionToWideX2<DFromV<V>>> SumsOf4(V v) {
  return detail::SumsOf4(hwy::TypeTag<TFromV<V>>(),
                         hwy::SizeTag<sizeof(TFromV<V>)>(), v);
}

// ------------------------------ OrderedTruncate2To

#if HWY_IDE || \
    (defined(HWY_NATIVE_ORDERED_TRUNCATE_2_TO) == defined(HWY_TARGET_TOGGLE))

#ifdef HWY_NATIVE_ORDERED_TRUNCATE_2_TO
#undef HWY_NATIVE_ORDERED_TRUNCATE_2_TO
#else
#define HWY_NATIVE_ORDERED_TRUNCATE_2_TO
#endif

// (Must come after HWY_TARGET_TOGGLE, else we don't reset it for scalar)
#if HWY_TARGET != HWY_SCALAR || HWY_IDE
template <class DN, HWY_IF_UNSIGNED_D(DN), class V, HWY_IF_UNSIGNED_V(V),
          HWY_IF_T_SIZE_V(V, sizeof(TFromD<DN>) * 2),
          HWY_IF_LANES_D(DFromV<VFromD<DN>>, HWY_MAX_LANES_D(DFromV<V>) * 2)>
HWY_API VFromD<DN> OrderedTruncate2To(DN dn, V a, V b) {
  return ConcatEven(dn, BitCast(dn, b), BitCast(dn, a));
}
#endif  // HWY_TARGET != HWY_SCALAR
#endif  // HWY_NATIVE_ORDERED_TRUNCATE_2_TO

// -------------------- LeadingZeroCount, TrailingZeroCount, HighestSetBitIndex

#if (defined(HWY_NATIVE_LEADING_ZERO_COUNT) == defined(HWY_TARGET_TOGGLE))
#ifdef HWY_NATIVE_LEADING_ZERO_COUNT
#undef HWY_NATIVE_LEADING_ZERO_COUNT
#else
#define HWY_NATIVE_LEADING_ZERO_COUNT
#endif

namespace detail {

template <class D, HWY_IF_U32_D(D)>
HWY_INLINE VFromD<D> UIntToF32BiasedExp(D d, VFromD<D> v) {
  const RebindToFloat<decltype(d)> df;
#if HWY_TARGET > HWY_AVX3 && HWY_TARGET <= HWY_SSE2
  const RebindToSigned<decltype(d)> di;
  const Repartition<int16_t, decltype(d)> di16;

  // On SSE2/SSSE3/SSE4/AVX2, do an int32_t to float conversion, followed
  // by a unsigned right shift of the uint32_t bit representation of the
  // floating point values by 23, followed by an int16_t Min
  // operation as we are only interested in the biased exponent that would
  // result from a uint32_t to float conversion.

  // An int32_t to float vector conversion is also much more efficient on
  // SSE2/SSSE3/SSE4/AVX2 than an uint32_t vector to float vector conversion
  // as an uint32_t vector to float vector conversion on SSE2/SSSE3/SSE4/AVX2
  // requires multiple instructions whereas an int32_t to float vector
  // conversion can be carried out using a single instruction on
  // SSE2/SSSE3/SSE4/AVX2.

  const auto f32_bits = BitCast(d, ConvertTo(df, BitCast(di, v)));
  return BitCast(d, Min(BitCast(di16, ShiftRight<23>(f32_bits)),
                        BitCast(di16, Set(d, 158))));
#else
  const auto f32_bits = BitCast(d, ConvertTo(df, v));
  return BitCast(d, ShiftRight<23>(f32_bits));
#endif
}

template <class V, HWY_IF_U32_D(DFromV<V>)>
HWY_INLINE V I32RangeU32ToF32BiasedExp(V v) {
  // I32RangeU32ToF32BiasedExp is similar to UIntToF32BiasedExp, but
  // I32RangeU32ToF32BiasedExp assumes that v[i] is between 0 and 2147483647.
  const DFromV<decltype(v)> d;
  const RebindToFloat<decltype(d)> df;
#if HWY_TARGET > HWY_AVX3 && HWY_TARGET <= HWY_SSE2
  const RebindToSigned<decltype(d)> d_src;
#else
  const RebindToUnsigned<decltype(d)> d_src;
#endif
  const auto f32_bits = BitCast(d, ConvertTo(df, BitCast(d_src, v)));
  return ShiftRight<23>(f32_bits);
}

template <class D, HWY_IF_U16_D(D), HWY_IF_LANES_LE_D(D, HWY_MAX_BYTES / 4)>
HWY_INLINE VFromD<D> UIntToF32BiasedExp(D d, VFromD<D> v) {
  const Rebind<uint32_t, decltype(d)> du32;
  const auto f32_biased_exp_as_u32 =
      I32RangeU32ToF32BiasedExp(PromoteTo(du32, v));
  return TruncateTo(d, f32_biased_exp_as_u32);
}

#if HWY_TARGET != HWY_SCALAR
template <class D, HWY_IF_U16_D(D), HWY_IF_LANES_GT_D(D, HWY_MAX_BYTES / 4)>
HWY_INLINE VFromD<D> UIntToF32BiasedExp(D d, VFromD<D> v) {
  const Half<decltype(d)> dh;
  const Rebind<uint32_t, decltype(dh)> du32;

  const auto lo_u32 = PromoteTo(du32, LowerHalf(dh, v));
  const auto hi_u32 = PromoteTo(du32, UpperHalf(dh, v));

  const auto lo_f32_biased_exp_as_u32 = I32RangeU32ToF32BiasedExp(lo_u32);
  const auto hi_f32_biased_exp_as_u32 = I32RangeU32ToF32BiasedExp(hi_u32);
#if HWY_TARGET <= HWY_SSE2
  const RebindToSigned<decltype(du32)> di32;
  const RebindToSigned<decltype(d)> di;
  return BitCast(d,
                 OrderedDemote2To(di, BitCast(di32, lo_f32_biased_exp_as_u32),
                                  BitCast(di32, hi_f32_biased_exp_as_u32)));
#else
  return OrderedTruncate2To(d, lo_f32_biased_exp_as_u32,
                            hi_f32_biased_exp_as_u32);
#endif
}
#endif  // HWY_TARGET != HWY_SCALAR

template <class D, HWY_IF_U8_D(D), HWY_IF_LANES_LE_D(D, HWY_MAX_BYTES / 4)>
HWY_INLINE VFromD<D> UIntToF32BiasedExp(D d, VFromD<D> v) {
  const Rebind<uint32_t, decltype(d)> du32;
  const auto f32_biased_exp_as_u32 =
      I32RangeU32ToF32BiasedExp(PromoteTo(du32, v));
  return U8FromU32(f32_biased_exp_as_u32);
}

#if HWY_TARGET != HWY_SCALAR
template <class D, HWY_IF_U8_D(D), HWY_IF_LANES_GT_D(D, HWY_MAX_BYTES / 4),
          HWY_IF_LANES_LE_D(D, HWY_MAX_BYTES / 2)>
HWY_INLINE VFromD<D> UIntToF32BiasedExp(D d, VFromD<D> v) {
  const Half<decltype(d)> dh;
  const Rebind<uint32_t, decltype(dh)> du32;
  const Repartition<uint16_t, decltype(du32)> du16;

  const auto lo_u32 = PromoteTo(du32, LowerHalf(dh, v));
  const auto hi_u32 = PromoteTo(du32, UpperHalf(dh, v));

  const auto lo_f32_biased_exp_as_u32 = I32RangeU32ToF32BiasedExp(lo_u32);
  const auto hi_f32_biased_exp_as_u32 = I32RangeU32ToF32BiasedExp(hi_u32);

#if HWY_TARGET <= HWY_SSE2
  const RebindToSigned<decltype(du32)> di32;
  const RebindToSigned<decltype(du16)> di16;
  const auto f32_biased_exp_as_i16 =
      OrderedDemote2To(di16, BitCast(di32, lo_f32_biased_exp_as_u32),
                       BitCast(di32, hi_f32_biased_exp_as_u32));
  return DemoteTo(d, f32_biased_exp_as_i16);
#else
  const auto f32_biased_exp_as_u16 = OrderedTruncate2To(
      du16, lo_f32_biased_exp_as_u32, hi_f32_biased_exp_as_u32);
  return TruncateTo(d, f32_biased_exp_as_u16);
#endif
}

template <class D, HWY_IF_U8_D(D), HWY_IF_LANES_GT_D(D, HWY_MAX_BYTES / 2)>
HWY_INLINE VFromD<D> UIntToF32BiasedExp(D d, VFromD<D> v) {
  const Half<decltype(d)> dh;
  const Half<decltype(dh)> dq;
  const Rebind<uint32_t, decltype(dq)> du32;
  const Repartition<uint16_t, decltype(du32)> du16;

  const auto lo_half = LowerHalf(dh, v);
  const auto hi_half = UpperHalf(dh, v);

  const auto u32_q0 = PromoteTo(du32, LowerHalf(dq, lo_half));
  const auto u32_q1 = PromoteTo(du32, UpperHalf(dq, lo_half));
  const auto u32_q2 = PromoteTo(du32, LowerHalf(dq, hi_half));
  const auto u32_q3 = PromoteTo(du32, UpperHalf(dq, hi_half));

  const auto f32_biased_exp_as_u32_q0 = I32RangeU32ToF32BiasedExp(u32_q0);
  const auto f32_biased_exp_as_u32_q1 = I32RangeU32ToF32BiasedExp(u32_q1);
  const auto f32_biased_exp_as_u32_q2 = I32RangeU32ToF32BiasedExp(u32_q2);
  const auto f32_biased_exp_as_u32_q3 = I32RangeU32ToF32BiasedExp(u32_q3);

#if HWY_TARGET <= HWY_SSE2
  const RebindToSigned<decltype(du32)> di32;
  const RebindToSigned<decltype(du16)> di16;

  const auto lo_f32_biased_exp_as_i16 =
      OrderedDemote2To(di16, BitCast(di32, f32_biased_exp_as_u32_q0),
                       BitCast(di32, f32_biased_exp_as_u32_q1));
  const auto hi_f32_biased_exp_as_i16 =
      OrderedDemote2To(di16, BitCast(di32, f32_biased_exp_as_u32_q2),
                       BitCast(di32, f32_biased_exp_as_u32_q3));
  return OrderedDemote2To(d, lo_f32_biased_exp_as_i16,
                          hi_f32_biased_exp_as_i16);
#else
  const auto lo_f32_biased_exp_as_u16 = OrderedTruncate2To(
      du16, f32_biased_exp_as_u32_q0, f32_biased_exp_as_u32_q1);
  const auto hi_f32_biased_exp_as_u16 = OrderedTruncate2To(
      du16, f32_biased_exp_as_u32_q2, f32_biased_exp_as_u32_q3);
  return OrderedTruncate2To(d, lo_f32_biased_exp_as_u16,
                            hi_f32_biased_exp_as_u16);
#endif
}
#endif  // HWY_TARGET != HWY_SCALAR

#if HWY_TARGET == HWY_SCALAR
template <class D>
using F32ExpLzcntMinMaxRepartition = RebindToUnsigned<D>;
#elif HWY_TARGET >= HWY_SSSE3 && HWY_TARGET <= HWY_SSE2
template <class D>
using F32ExpLzcntMinMaxRepartition = Repartition<uint8_t, D>;
#else
template <class D>
using F32ExpLzcntMinMaxRepartition =
    Repartition<UnsignedFromSize<HWY_MIN(sizeof(TFromD<D>), 4)>, D>;
#endif

template <class V>
using F32ExpLzcntMinMaxCmpV = VFromD<F32ExpLzcntMinMaxRepartition<DFromV<V>>>;

template <class V>
HWY_INLINE F32ExpLzcntMinMaxCmpV<V> F32ExpLzcntMinMaxBitCast(V v) {
  const DFromV<decltype(v)> d;
  const F32ExpLzcntMinMaxRepartition<decltype(d)> d2;
  return BitCast(d2, v);
}

template <class D, HWY_IF_U64_D(D)>
HWY_INLINE VFromD<D> UIntToF32BiasedExp(D d, VFromD<D> v) {
#if HWY_TARGET == HWY_SCALAR
  const uint64_t u64_val = GetLane(v);
  const float f32_val = static_cast<float>(u64_val);
  const uint32_t f32_bits = BitCastScalar<uint32_t>(f32_val);
  return Set(d, static_cast<uint64_t>(f32_bits >> 23));
#else
  const Repartition<uint32_t, decltype(d)> du32;
  const auto f32_biased_exp = UIntToF32BiasedExp(du32, BitCast(du32, v));
  const auto f32_biased_exp_adj =
      IfThenZeroElse(Eq(f32_biased_exp, Zero(du32)),
                     BitCast(du32, Set(d, 0x0000002000000000u)));
  const auto adj_f32_biased_exp = Add(f32_biased_exp, f32_biased_exp_adj);

  return ShiftRight<32>(BitCast(
      d, Max(F32ExpLzcntMinMaxBitCast(adj_f32_biased_exp),
             F32ExpLzcntMinMaxBitCast(Reverse2(du32, adj_f32_biased_exp)))));
#endif
}

template <class V, HWY_IF_UNSIGNED_V(V)>
HWY_INLINE V UIntToF32BiasedExp(V v) {
  const DFromV<decltype(v)> d;
  return UIntToF32BiasedExp(d, v);
}

template <class V, HWY_IF_UNSIGNED_V(V),
          HWY_IF_T_SIZE_ONE_OF_V(V, (1 << 1) | (1 << 2))>
HWY_INLINE V NormalizeForUIntTruncConvToF32(V v) {
  return v;
}

template <class V, HWY_IF_UNSIGNED_V(V),
          HWY_IF_T_SIZE_ONE_OF_V(V, (1 << 4) | (1 << 8))>
HWY_INLINE V NormalizeForUIntTruncConvToF32(V v) {
  // If v[i] >= 16777216 is true, make sure that the bit at
  // HighestSetBitIndex(v[i]) - 24 is zeroed out to ensure that any inexact
  // conversion to single-precision floating point is rounded down.

  // This zeroing-out can be accomplished through the AndNot operation below.
  return AndNot(ShiftRight<24>(v), v);
}

}  // namespace detail

template <class V, HWY_IF_NOT_FLOAT_NOR_SPECIAL_V(V)>
HWY_API V HighestSetBitIndex(V v) {
  const DFromV<decltype(v)> d;
  const RebindToUnsigned<decltype(d)> du;
  using TU = TFromD<decltype(du)>;

  const auto f32_biased_exp = detail::UIntToF32BiasedExp(
      detail::NormalizeForUIntTruncConvToF32(BitCast(du, v)));
  return BitCast(d, Sub(f32_biased_exp, Set(du, TU{127})));
}

template <class V, HWY_IF_NOT_FLOAT_NOR_SPECIAL_V(V)>
HWY_API V LeadingZeroCount(V v) {
  const DFromV<decltype(v)> d;
  const RebindToUnsigned<decltype(d)> du;
  using TU = TFromD<decltype(du)>;

  constexpr TU kNumOfBitsInT{sizeof(TU) * 8};
  const auto f32_biased_exp = detail::UIntToF32BiasedExp(
      detail::NormalizeForUIntTruncConvToF32(BitCast(du, v)));
  const auto lz_count = Sub(Set(du, TU{kNumOfBitsInT + 126}), f32_biased_exp);

  return BitCast(d,
                 Min(detail::F32ExpLzcntMinMaxBitCast(lz_count),
                     detail::F32ExpLzcntMinMaxBitCast(Set(du, kNumOfBitsInT))));
}

template <class V, HWY_IF_NOT_FLOAT_NOR_SPECIAL_V(V)>
HWY_API V TrailingZeroCount(V v) {
  const DFromV<decltype(v)> d;
  const RebindToUnsigned<decltype(d)> du;
  const RebindToSigned<decltype(d)> di;
  using TU = TFromD<decltype(du)>;

  const auto vi = BitCast(di, v);
  const auto lowest_bit = BitCast(du, And(vi, Neg(vi)));

  constexpr TU kNumOfBitsInT{sizeof(TU) * 8};
  const auto f32_biased_exp = detail::UIntToF32BiasedExp(lowest_bit);
  const auto tz_count = Sub(f32_biased_exp, Set(du, TU{127}));

  return BitCast(d,
                 Min(detail::F32ExpLzcntMinMaxBitCast(tz_count),
                     detail::F32ExpLzcntMinMaxBitCast(Set(du, kNumOfBitsInT))));
}
#endif  // HWY_NATIVE_LEADING_ZERO_COUNT

// ------------------------------ AESRound

// Cannot implement on scalar: need at least 16 bytes for TableLookupBytes.
#if HWY_TARGET != HWY_SCALAR || HWY_IDE

// Define for white-box testing, even if native instructions are available.
namespace detail {

// Constant-time: computes inverse in GF(2^4) based on "Accelerating AES with
// Vector Permute Instructions" and the accompanying assembly language
// implementation: https://crypto.stanford.edu/vpaes/vpaes.tgz. See also Botan:
// https://botan.randombit.net/doxygen/aes__vperm_8cpp_source.html .
//
// A brute-force 256 byte table lookup can also be made constant-time, and
// possibly competitive on NEON, but this is more performance-portable
// especially for x86 and large vectors.

template <class V>  // u8
HWY_INLINE V SubBytesMulInverseAndAffineLookup(V state, V affine_tblL,
                                               V affine_tblU) {
  const DFromV<V> du;
  const auto mask = Set(du, uint8_t{0xF});

  // Change polynomial basis to GF(2^4)
  {
    const VFromD<decltype(du)> basisL =
        Dup128VecFromValues(du, 0x00, 0x70, 0x2A, 0x5A, 0x98, 0xE8, 0xB2, 0xC2,
                            0x08, 0x78, 0x22, 0x52, 0x90, 0xE0, 0xBA, 0xCA);
    const VFromD<decltype(du)> basisU =
        Dup128VecFromValues(du, 0x00, 0x4D, 0x7C, 0x31, 0x7D, 0x30, 0x01, 0x4C,
                            0x81, 0xCC, 0xFD, 0xB0, 0xFC, 0xB1, 0x80, 0xCD);
    const auto sL = And(state, mask);
    const auto sU = ShiftRight<4>(state);  // byte shift => upper bits are zero
    const auto gf4L = TableLookupBytes(basisL, sL);
    const auto gf4U = TableLookupBytes(basisU, sU);
    state = Xor(gf4L, gf4U);
  }

  // Inversion in GF(2^4). Elements 0 represent "infinity" (division by 0) and
  // cause TableLookupBytesOr0 to return 0.
  const VFromD<decltype(du)> zetaInv = Dup128VecFromValues(
      du, 0x80, 711156104198521214133);
  const VFromD<decltype(du)> tbl = Dup128VecFromValues(
      du, 0x80, 181315651421211109374);
  const auto sL = And(state, mask);      // L=low nibble, U=upper
  const auto sU = ShiftRight<4>(state);  // byte shift => upper bits are zero
  const auto sX = Xor(sU, sL);
  const auto invL = TableLookupBytes(zetaInv, sL);
  const auto invU = TableLookupBytes(tbl, sU);
  const auto invX = TableLookupBytes(tbl, sX);
  const auto outL = Xor(sX, TableLookupBytesOr0(tbl, Xor(invL, invU)));
  const auto outU = Xor(sU, TableLookupBytesOr0(tbl, Xor(invL, invX)));

  const auto affL = TableLookupBytesOr0(affine_tblL, outL);
  const auto affU = TableLookupBytesOr0(affine_tblU, outU);
  return Xor(affL, affU);
}

template <class V>  // u8
HWY_INLINE V SubBytes(V state) {
  const DFromV<V> du;
  // Linear skew (cannot bake 0x63 bias into the table because out* indices
  // may have the infinity flag set).
  const VFromD<decltype(du)> affineL =
      Dup128VecFromValues(du, 0x00, 0xC7, 0xBD, 0x6F, 0x17, 0x6D, 0xD2, 0xD0,
                          0x78, 0xA8, 0x02, 0xC5, 0x7A, 0xBF, 0xAA, 0x15);
  const VFromD<decltype(du)> affineU =
      Dup128VecFromValues(du, 0x00, 0x6A, 0xBB, 0x5F, 0xA5, 0x74, 0xE4, 0xCF,
                          0xFA, 0x35, 0x2B, 0x41, 0xD1, 0x90, 0x1E, 0x8E);
  return Xor(SubBytesMulInverseAndAffineLookup(state, affineL, affineU),
             Set(du, uint8_t{0x63}));
}

template <class V>  // u8
HWY_INLINE V InvSubBytes(V state) {
  const DFromV<V> du;
  const VFromD<decltype(du)> gF2P4InvToGF2P8InvL =
      Dup128VecFromValues(du, 0x00, 0x40, 0xF9, 0x7E, 0x53, 0xEA, 0x87, 0x13,
                          0x2D, 0x3E, 0x94, 0xD4, 0xB9, 0x6D, 0xAA, 0xC7);
  const VFromD<decltype(du)> gF2P4InvToGF2P8InvU =
      Dup128VecFromValues(du, 0x00, 0x1D, 0x44, 0x93, 0x0F, 0x56, 0xD7, 0x12,
                          0x9C, 0x8E, 0xC5, 0xD8, 0x59, 0x81, 0x4B, 0xCA);

  // Apply the inverse affine transformation
  const auto b = Xor(Xor3(Or(ShiftLeft<1>(state), ShiftRight<7>(state)),
                          Or(ShiftLeft<3>(state), ShiftRight<5>(state)),
                          Or(ShiftLeft<6>(state), ShiftRight<2>(state))),
                     Set(du, uint8_t{0x05}));

  // The GF(2^8) multiplicative inverse is computed as follows:
  // - Changing the polynomial basis to GF(2^4)
  // - Computing the GF(2^4) multiplicative inverse
  // - Converting the GF(2^4) multiplicative inverse to the GF(2^8)
  //   multiplicative inverse through table lookups using the
  //   kGF2P4InvToGF2P8InvL and kGF2P4InvToGF2P8InvU tables
  return SubBytesMulInverseAndAffineLookup(b, gF2P4InvToGF2P8InvL,
                                           gF2P4InvToGF2P8InvU);
}

}  // namespace detail

#endif  // HWY_TARGET != HWY_SCALAR

#if (defined(HWY_NATIVE_AES) == defined(HWY_TARGET_TOGGLE))
#ifdef HWY_NATIVE_AES
#undef HWY_NATIVE_AES
#else
#define HWY_NATIVE_AES
#endif

// (Must come after HWY_TARGET_TOGGLE, else we don't reset it for scalar)
#if HWY_TARGET != HWY_SCALAR

namespace detail {

template <class V>  // u8
HWY_INLINE V ShiftRows(const V state) {
  const DFromV<V> du;
  // transposed: state is column major
  const VFromD<decltype(du)> shift_row = Dup128VecFromValues(
      du, 0510154914381327121611);
  return TableLookupBytes(state, shift_row);
}

template <class V>  // u8
HWY_INLINE V InvShiftRows(const V state) {
  const DFromV<V> du;
  // transposed: state is column major
  const VFromD<decltype(du)> shift_row = Dup128VecFromValues(
      du, 0131074114118521512963);
  return TableLookupBytes(state, shift_row);
}

template <class V>  // u8
HWY_INLINE V GF2P8Mod11BMulBy2(V v) {
  const DFromV<V> du;
  const RebindToSigned<decltype(du)> di;  // can only do signed comparisons
  const auto msb = Lt(BitCast(di, v), Zero(di));
  const auto overflow = BitCast(du, IfThenElseZero(msb, Set(di, int8_t{0x1B})));
  return Xor(Add(v, v), overflow);  // = v*2 in GF(2^8).
}

template <class V>  // u8
HWY_INLINE V MixColumns(const V state) {
  const DFromV<V> du;
  // For each column, the rows are the sum of GF(2^8) matrix multiplication by:
  // 2 3 1 1  // Let s := state*1, d := state*2, t := state*3.
  // 1 2 3 1  // d are on diagonal, no permutation needed.
  // 1 1 2 3  // t1230 indicates column indices of threes for the 4 rows.
  // 3 1 1 2  // We also need to compute s2301 and s3012 (=1230 o 2301).
  const VFromD<decltype(du)> v2301 = Dup128VecFromValues(
      du, 2301674510118914151213);
  const VFromD<decltype(du)> v1230 = Dup128VecFromValues(
      du, 1230567491011813141512);
  const auto d = GF2P8Mod11BMulBy2(state);  // = state*2 in GF(2^8).
  const auto s2301 = TableLookupBytes(state, v2301);
  const auto d_s2301 = Xor(d, s2301);
  const auto t_s2301 = Xor(state, d_s2301);  // t(s*3) = XOR-sum {s, d(s*2)}
  const auto t1230_s3012 = TableLookupBytes(t_s2301, v1230);
  return Xor(d_s2301, t1230_s3012);  // XOR-sum of 4 terms
}

template <class V>  // u8
HWY_INLINE V InvMixColumns(const V state) {
  const DFromV<V> du;
  // For each column, the rows are the sum of GF(2^8) matrix multiplication by:
  // 14 11 13  9
  //  9 14 11 13
  // 13  9 14 11
  // 11 13  9 14
  const VFromD<decltype(du)> v2301 = Dup128VecFromValues(
      du, 2301674510118914151213);
  const VFromD<decltype(du)> v1230 = Dup128VecFromValues(
      du, 1230567491011813141512);

  const auto sx2 = GF2P8Mod11BMulBy2(state); /* = state*2 in GF(2^8) */
  const auto sx4 = GF2P8Mod11BMulBy2(sx2);   /* = state*4 in GF(2^8) */
  const auto sx8 = GF2P8Mod11BMulBy2(sx4);   /* = state*8 in GF(2^8) */
  const auto sx9 = Xor(sx8, state);          /* = state*9 in GF(2^8) */
  const auto sx11 = Xor(sx9, sx2);           /* = state*11 in GF(2^8) */
  const auto sx13 = Xor(sx9, sx4);           /* = state*13 in GF(2^8) */
  const auto sx14 = Xor3(sx8, sx4, sx2);     /* = state*14 in GF(2^8) */

  const auto sx13_0123_sx9_1230 = Xor(sx13, TableLookupBytes(sx9, v1230));
  const auto sx14_0123_sx11_1230 = Xor(sx14, TableLookupBytes(sx11, v1230));
  const auto sx13_2301_sx9_3012 = TableLookupBytes(sx13_0123_sx9_1230, v2301);
  return Xor(sx14_0123_sx11_1230, sx13_2301_sx9_3012);
}

}  // namespace detail

template <class V>  // u8
HWY_API V AESRound(V state, const V round_key) {
  // Intel docs swap the first two steps, but it does not matter because
  // ShiftRows is a permutation and SubBytes is independent of lane index.
  state = detail::SubBytes(state);
  state = detail::ShiftRows(state);
  state = detail::MixColumns(state);
  state = Xor(state, round_key);  // AddRoundKey
  return state;
}

template <class V>  // u8
HWY_API V AESLastRound(V state, const V round_key) {
  // LIke AESRound, but without MixColumns.
  state = detail::SubBytes(state);
  state = detail::ShiftRows(state);
  state = Xor(state, round_key);  // AddRoundKey
  return state;
}

template <class V>
HWY_API V AESInvMixColumns(V state) {
  return detail::InvMixColumns(state);
}

template <class V>  // u8
HWY_API V AESRoundInv(V state, const V round_key) {
  state = detail::InvSubBytes(state);
  state = detail::InvShiftRows(state);
  state = detail::InvMixColumns(state);
  state = Xor(state, round_key);  // AddRoundKey
  return state;
}

template <class V>  // u8
HWY_API V AESLastRoundInv(V state, const V round_key) {
  // Like AESRoundInv, but without InvMixColumns.
  state = detail::InvSubBytes(state);
  state = detail::InvShiftRows(state);
  state = Xor(state, round_key);  // AddRoundKey
  return state;
}

template <uint8_t kRcon, class V, HWY_IF_U8_D(DFromV<V>)>
HWY_API V AESKeyGenAssist(V v) {
  const DFromV<decltype(v)> d;
  const V rconXorMask = Dup128VecFromValues(d, 0000, kRcon, 00000,
                                            00, kRcon, 000);
  const V rotWordShuffle = Dup128VecFromValues(d, 4567567412,
                                               13141513141512);
  const auto sub_word_result = detail::SubBytes(v);
  const auto rot_word_result =
      TableLookupBytes(sub_word_result, rotWordShuffle);
  return Xor(rot_word_result, rconXorMask);
}

// Constant-time implementation inspired by
// https://www.bearssl.org/constanttime.html, but about half the cost because we
// use 64x64 multiplies and 128-bit XORs.
template <class V>
HWY_API V CLMulLower(V a, V b) {
  const DFromV<V> d;
  static_assert(IsSame<TFromD<decltype(d)>, uint64_t>(), "V must be u64");
  const auto k1 = Set(d, 0x1111111111111111ULL);
  const auto k2 = Set(d, 0x2222222222222222ULL);
  const auto k4 = Set(d, 0x4444444444444444ULL);
  const auto k8 = Set(d, 0x8888888888888888ULL);
  const auto a0 = And(a, k1);
  const auto a1 = And(a, k2);
  const auto a2 = And(a, k4);
  const auto a3 = And(a, k8);
  const auto b0 = And(b, k1);
  const auto b1 = And(b, k2);
  const auto b2 = And(b, k4);
  const auto b3 = And(b, k8);

  auto m0 = Xor(MulEven(a0, b0), MulEven(a1, b3));
  auto m1 = Xor(MulEven(a0, b1), MulEven(a1, b0));
  auto m2 = Xor(MulEven(a0, b2), MulEven(a1, b1));
  auto m3 = Xor(MulEven(a0, b3), MulEven(a1, b2));
  m0 = Xor(m0, Xor(MulEven(a2, b2), MulEven(a3, b1)));
  m1 = Xor(m1, Xor(MulEven(a2, b3), MulEven(a3, b2)));
  m2 = Xor(m2, Xor(MulEven(a2, b0), MulEven(a3, b3)));
  m3 = Xor(m3, Xor(MulEven(a2, b1), MulEven(a3, b0)));
  return Or(Or(And(m0, k1), And(m1, k2)), Or(And(m2, k4), And(m3, k8)));
}

template <class V>
HWY_API V CLMulUpper(V a, V b) {
  const DFromV<V> d;
  static_assert(IsSame<TFromD<decltype(d)>, uint64_t>(), "V must be u64");
  const auto k1 = Set(d, 0x1111111111111111ULL);
  const auto k2 = Set(d, 0x2222222222222222ULL);
  const auto k4 = Set(d, 0x4444444444444444ULL);
  const auto k8 = Set(d, 0x8888888888888888ULL);
  const auto a0 = And(a, k1);
  const auto a1 = And(a, k2);
  const auto a2 = And(a, k4);
  const auto a3 = And(a, k8);
  const auto b0 = And(b, k1);
  const auto b1 = And(b, k2);
  const auto b2 = And(b, k4);
  const auto b3 = And(b, k8);

  auto m0 = Xor(MulOdd(a0, b0), MulOdd(a1, b3));
  auto m1 = Xor(MulOdd(a0, b1), MulOdd(a1, b0));
  auto m2 = Xor(MulOdd(a0, b2), MulOdd(a1, b1));
  auto m3 = Xor(MulOdd(a0, b3), MulOdd(a1, b2));
  m0 = Xor(m0, Xor(MulOdd(a2, b2), MulOdd(a3, b1)));
  m1 = Xor(m1, Xor(MulOdd(a2, b3), MulOdd(a3, b2)));
  m2 = Xor(m2, Xor(MulOdd(a2, b0), MulOdd(a3, b3)));
  m3 = Xor(m3, Xor(MulOdd(a2, b1), MulOdd(a3, b0)));
  return Or(Or(And(m0, k1), And(m1, k2)), Or(And(m2, k4), And(m3, k8)));
}

#endif  // HWY_NATIVE_AES
#endif  // HWY_TARGET != HWY_SCALAR

// ------------------------------ PopulationCount

#if (defined(HWY_NATIVE_POPCNT) == defined(HWY_TARGET_TOGGLE))
#ifdef HWY_NATIVE_POPCNT
#undef HWY_NATIVE_POPCNT
#else
#define HWY_NATIVE_POPCNT
#endif

// This overload requires vectors to be at least 16 bytes, which is the case
// for LMUL >= 2.
#undef HWY_IF_POPCNT
#if HWY_TARGET == HWY_RVV
#define HWY_IF_POPCNT(D) \
  hwy::EnableIf<D().Pow2() >= 1 && D().MaxLanes() >= 16>* = nullptr
#else
// Other targets only have these two overloads which are mutually exclusive, so
// no further conditions are required.
#define HWY_IF_POPCNT(D) void* = nullptr
#endif  // HWY_TARGET == HWY_RVV

template <class V, class D = DFromV<V>, HWY_IF_U8_D(D),
          HWY_IF_V_SIZE_GT_D(D, 8), HWY_IF_POPCNT(D)>
HWY_API V PopulationCount(V v) {
  const D d;
  const V lookup =
      Dup128VecFromValues(d, 0112122312232334);
  const auto lo = And(v, Set(d, uint8_t{0xF}));
  const auto hi = ShiftRight<4>(v);
  return Add(TableLookupBytes(lookup, hi), TableLookupBytes(lookup, lo));
}

// RVV has a specialization that avoids the Set().
#if HWY_TARGET != HWY_RVV
// Slower fallback for capped vectors.
template <class V, class D = DFromV<V>, HWY_IF_U8_D(D),
          HWY_IF_V_SIZE_LE_D(D, 8)>
HWY_API V PopulationCount(V v) {
  const D d;
  // See https://arxiv.org/pdf/1611.07612.pdf, Figure 3
  const V k33 = Set(d, uint8_t{0x33});
  v = Sub(v, And(ShiftRight<1>(v), Set(d, uint8_t{0x55})));
  v = Add(And(ShiftRight<2>(v), k33), And(v, k33));
  return And(Add(v, ShiftRight<4>(v)), Set(d, uint8_t{0x0F}));
}
#endif  // HWY_TARGET != HWY_RVV

template <class V, class D = DFromV<V>, HWY_IF_U16_D(D)>
HWY_API V PopulationCount(V v) {
  const D d;
  const Repartition<uint8_t, decltype(d)> d8;
  const auto vals = BitCast(d, PopulationCount(BitCast(d8, v)));
  return Add(ShiftRight<8>(vals), And(vals, Set(d, uint16_t{0xFF})));
}

template <class V, class D = DFromV<V>, HWY_IF_U32_D(D)>
HWY_API V PopulationCount(V v) {
  const D d;
  Repartition<uint16_t, decltype(d)> d16;
  auto vals = BitCast(d, PopulationCount(BitCast(d16, v)));
  return Add(ShiftRight<16>(vals), And(vals, Set(d, uint32_t{0xFF})));
}

#if HWY_HAVE_INTEGER64
template <class V, class D = DFromV<V>, HWY_IF_U64_D(D)>
HWY_API V PopulationCount(V v) {
  const D d;
  Repartition<uint32_t, decltype(d)> d32;
  auto vals = BitCast(d, PopulationCount(BitCast(d32, v)));
  return Add(ShiftRight<32>(vals), And(vals, Set(d, 0xFFULL)));
}
#endif

#endif  // HWY_NATIVE_POPCNT

// ------------------------------ 8-bit multiplication

#if (defined(HWY_NATIVE_MUL_8) == defined(HWY_TARGET_TOGGLE)) || HWY_IDE
#ifdef HWY_NATIVE_MUL_8
#undef HWY_NATIVE_MUL_8
#else
#define HWY_NATIVE_MUL_8
#endif

// 8 bit and fits in wider reg: promote
template <class V, HWY_IF_T_SIZE_V(V, 1),
          HWY_IF_V_SIZE_LE_V(V, HWY_MAX_BYTES / 2)>
HWY_API V operator*(const V a, const V b) {
  const DFromV<decltype(a)> d;
  const Rebind<MakeWide<TFromV<V>>, decltype(d)> dw;
  const RebindToUnsigned<decltype(d)> du;    // TruncateTo result
  const RebindToUnsigned<decltype(dw)> dwu;  // TruncateTo input
  const VFromD<decltype(dw)> mul = PromoteTo(dw, a) * PromoteTo(dw, b);
  // TruncateTo is cheaper than ConcatEven.
  return BitCast(d, TruncateTo(du, BitCast(dwu, mul)));
}

// 8 bit full reg: promote halves
template <class V, HWY_IF_T_SIZE_V(V, 1),
          HWY_IF_V_SIZE_GT_V(V, HWY_MAX_BYTES / 2)>
HWY_API V operator*(const V a, const V b) {
  const DFromV<decltype(a)> d;
  const Half<decltype(d)> dh;
  const Twice<RepartitionToWide<decltype(dh)>> dw;
  const VFromD<decltype(dw)> a0 = PromoteTo(dw, LowerHalf(dh, a));
  const VFromD<decltype(dw)> a1 = PromoteTo(dw, UpperHalf(dh, a));
  const VFromD<decltype(dw)> b0 = PromoteTo(dw, LowerHalf(dh, b));
  const VFromD<decltype(dw)> b1 = PromoteTo(dw, UpperHalf(dh, b));
  const VFromD<decltype(dw)> m0 = a0 * b0;
  const VFromD<decltype(dw)> m1 = a1 * b1;
  return ConcatEven(d, BitCast(d, m1), BitCast(d, m0));
}

#endif  // HWY_NATIVE_MUL_8

// ------------------------------ 64-bit multiplication

#if (defined(HWY_NATIVE_MUL_64) == defined(HWY_TARGET_TOGGLE)) || HWY_IDE
#ifdef HWY_NATIVE_MUL_64
#undef HWY_NATIVE_MUL_64
#else
#define HWY_NATIVE_MUL_64
#endif

// Single-lane i64 or u64
template <class V, HWY_IF_T_SIZE_V(V, 8), HWY_IF_V_SIZE_V(V, 8),
          HWY_IF_NOT_FLOAT_V(V)>
HWY_API V operator*(V x, V y) {
  const DFromV<V> d;
  using T = TFromD<decltype(d)>;
  using TU = MakeUnsigned<T>;
  const TU xu = static_cast<TU>(GetLane(x));
  const TU yu = static_cast<TU>(GetLane(y));
  return Set(d, static_cast<T>(xu * yu));
}

template <class V, class D64 = DFromV<V>, HWY_IF_U64_D(D64),
          HWY_IF_V_SIZE_GT_D(D64, 8)>
HWY_API V operator*(V x, V y) {
  RepartitionToNarrow<D64> d32;
  auto x32 = BitCast(d32, x);
  auto y32 = BitCast(d32, y);
  auto lolo = BitCast(d32, MulEven(x32, y32));
  auto lohi = BitCast(d32, MulEven(x32, BitCast(d32, ShiftRight<32>(y))));
  auto hilo = BitCast(d32, MulEven(BitCast(d32, ShiftRight<32>(x)), y32));
  auto hi = BitCast(d32, ShiftLeft<32>(BitCast(D64{}, lohi + hilo)));
  return BitCast(D64{}, lolo + hi);
}
template <class V, class DI64 = DFromV<V>, HWY_IF_I64_D(DI64),
          HWY_IF_V_SIZE_GT_D(DI64, 8)>
HWY_API V operator*(V x, V y) {
  RebindToUnsigned<DI64> du64;
  return BitCast(DI64{}, BitCast(du64, x) * BitCast(du64, y));
}

#endif  // HWY_NATIVE_MUL_64

// ------------------------------ MulAdd / NegMulAdd

#if (defined(HWY_NATIVE_INT_FMA) == defined(HWY_TARGET_TOGGLE))
#ifdef HWY_NATIVE_INT_FMA
#undef HWY_NATIVE_INT_FMA
#else
#define HWY_NATIVE_INT_FMA
#endif

#ifdef HWY_NATIVE_INT_FMSUB
#undef HWY_NATIVE_INT_FMSUB
#else
#define HWY_NATIVE_INT_FMSUB
#endif

template <class V, HWY_IF_NOT_FLOAT_NOR_SPECIAL_V(V)>
HWY_API V MulAdd(V mul, V x, V add) {
  return Add(Mul(mul, x), add);
}

template <class V, HWY_IF_NOT_FLOAT_NOR_SPECIAL_V(V)>
HWY_API V NegMulAdd(V mul, V x, V add) {
  return Sub(add, Mul(mul, x));
}

template <class V, HWY_IF_NOT_FLOAT_NOR_SPECIAL_V(V)>
HWY_API V MulSub(V mul, V x, V sub) {
  return Sub(Mul(mul, x), sub);
}
#endif  // HWY_NATIVE_INT_FMA

// ------------------------------ Integer MulSub / NegMulSub
#if (defined(HWY_NATIVE_INT_FMSUB) == defined(HWY_TARGET_TOGGLE))
#ifdef HWY_NATIVE_INT_FMSUB
#undef HWY_NATIVE_INT_FMSUB
#else
#define HWY_NATIVE_INT_FMSUB
#endif

template <class V, HWY_IF_NOT_FLOAT_NOR_SPECIAL_V(V)>
HWY_API V MulSub(V mul, V x, V sub) {
  const DFromV<decltype(mul)> d;
  const RebindToSigned<decltype(d)> di;
  return MulAdd(mul, x, BitCast(d, Neg(BitCast(di, sub))));
}

#endif  // HWY_NATIVE_INT_FMSUB

template <class V, HWY_IF_NOT_FLOAT_NOR_SPECIAL_V(V)>
HWY_API V NegMulSub(V mul, V x, V sub) {
  const DFromV<decltype(mul)> d;
  const RebindToSigned<decltype(d)> di;

  return BitCast(d, Neg(BitCast(di, MulAdd(mul, x, sub))));
}

// ------------------------------ MulAddSub

// MulAddSub(mul, x, sub_or_add) for a 1-lane vector is equivalent to
// MulSub(mul, x, sub_or_add)
template <class V, HWY_IF_LANES_D(DFromV<V>, 1)>
HWY_API V MulAddSub(V mul, V x, V sub_or_add) {
  return MulSub(mul, x, sub_or_add);
}

// MulAddSub for F16/F32/F64 vectors with 2 or more lanes on
// SSSE3/SSE4/AVX2/AVX3 is implemented in x86_128-inl.h, x86_256-inl.h, and
// x86_512-inl.h
template <class V, HWY_IF_LANES_GT_D(DFromV<V>, 1),
          HWY_IF_T_SIZE_ONE_OF_V(V, (1 << 1) | ((HWY_TARGET <= HWY_SSSE3 &&
                                                 hwy::IsFloat<TFromV<V>>())
                                                    ? 0
                                                    : ((1 << 2) | (1 << 4) |
                                                       (1 << 8))))>
HWY_API V MulAddSub(V mul, V x, V sub_or_add) {
  using D = DFromV<V>;
  using T = TFromD<D>;
  using TNegate = If<!IsSigned<T>(), MakeSigned<T>, T>;

  const D d;
  const Rebind<TNegate, D> d_negate;

  const auto add =
      OddEven(sub_or_add, BitCast(d, Neg(BitCast(d_negate, sub_or_add))));
  return MulAdd(mul, x, add);
}

// ------------------------------ Integer division
#if (defined(HWY_NATIVE_INT_DIV) == defined(HWY_TARGET_TOGGLE))
#ifdef HWY_NATIVE_INT_DIV
#undef HWY_NATIVE_INT_DIV
#else
#define HWY_NATIVE_INT_DIV
#endif

namespace detail {

template <class D, class V, HWY_IF_T_SIZE_D(D, sizeof(TFromV<V>))>
HWY_INLINE Vec<D> IntDivConvFloatToInt(D di, V vf) {
  return ConvertTo(di, vf);
}

template <class D, class V, HWY_IF_T_SIZE_D(D, sizeof(TFromV<V>))>
HWY_INLINE Vec<D> IntDivConvIntToFloat(D df, V vi) {
  return ConvertTo(df, vi);
}

#if !HWY_HAVE_FLOAT64 && HWY_HAVE_INTEGER64
template <class D, class V, HWY_IF_UI64_D(D), HWY_IF_F32(TFromV<V>)>
HWY_INLINE Vec<D> IntDivConvFloatToInt(D df, V vi) {
  return PromoteTo(df, vi);
}

// If !HWY_HAVE_FLOAT64 && HWY_HAVE_INTEGER64 is true, then UI64->F32
// IntDivConvIntToFloat(df, vi) returns an approximation of
// static_cast<float>(v[i]) that is within 4 ULP of static_cast<float>(v[i])
template <class D, class V, HWY_IF_F32_D(D), HWY_IF_I64(TFromV<V>)>
HWY_INLINE Vec<D> IntDivConvIntToFloat(D df32, V vi) {
  const Twice<decltype(df32)> dt_f32;

  auto vf32 =
      ConvertTo(dt_f32, BitCast(RebindToSigned<decltype(dt_f32)>(), vi));

#if HWY_IS_LITTLE_ENDIAN
  const auto lo_f32 = LowerHalf(df32, ConcatEven(dt_f32, vf32, vf32));
  auto hi_f32 = LowerHalf(df32, ConcatOdd(dt_f32, vf32, vf32));
#else
  const auto lo_f32 = LowerHalf(df32, ConcatOdd(dt_f32, vf32, vf32));
  auto hi_f32 = LowerHalf(df32, ConcatEven(dt_f32, vf32, vf32));
#endif

  const RebindToSigned<decltype(df32)> di32;

  hi_f32 =
      Add(hi_f32, And(BitCast(df32, BroadcastSignBit(BitCast(di32, lo_f32))),
                      Set(df32, 1.0f)));
  return hwy::HWY_NAMESPACE::MulAdd(hi_f32, Set(df32, 4294967296.0f), lo_f32);
}

template <class D, class V, HWY_IF_F32_D(D), HWY_IF_U64(TFromV<V>)>
HWY_INLINE Vec<D> IntDivConvIntToFloat(D df32, V vu) {
  const Twice<decltype(df32)> dt_f32;

  auto vf32 =
      ConvertTo(dt_f32, BitCast(RebindToUnsigned<decltype(dt_f32)>(), vu));

#if HWY_IS_LITTLE_ENDIAN
  const auto lo_f32 = LowerHalf(df32, ConcatEven(dt_f32, vf32, vf32));
  const auto hi_f32 = LowerHalf(df32, ConcatOdd(dt_f32, vf32, vf32));
#else
  const auto lo_f32 = LowerHalf(df32, ConcatOdd(dt_f32, vf32, vf32));
  const auto hi_f32 = LowerHalf(df32, ConcatEven(dt_f32, vf32, vf32));
#endif

  return hwy::HWY_NAMESPACE::MulAdd(hi_f32, Set(df32, 4294967296.0f), lo_f32);
}
#endif  // !HWY_HAVE_FLOAT64 && HWY_HAVE_INTEGER64

template <size_t kOrigLaneSize, class V, HWY_IF_NOT_FLOAT_NOR_SPECIAL_V(V),
          HWY_IF_T_SIZE_GT(TFromV<V>, kOrigLaneSize)>
HWY_INLINE V IntDivUsingFloatDiv(V a, V b) {
  const DFromV<decltype(a)> d;
  const RebindToFloat<decltype(d)> df;

  // If kOrigLaneSize < sizeof(T) is true, then a[i] and b[i] are both in the
  // [LimitsMin<SignedFromSize<kOrigLaneSize>>(),
  // LimitsMax<UnsignedFromSize<kOrigLaneSize>>()] range.

  // floor(|a[i] / b[i]|) <= |flt_q| < floor(|a[i] / b[i]|) + 1 is also
  // guaranteed to be true if MakeFloat<T> has at least kOrigLaneSize*8 + 1
  // mantissa bits (including the implied one bit), where flt_q is equal to
  // static_cast<MakeFloat<T>>(a[i]) / static_cast<MakeFloat<T>>(b[i]),
  // even in the case where the magnitude of an inexact floating point division
  // result is rounded up.

  // In other words, floor(flt_q) < flt_q < ceil(flt_q) is guaranteed to be true
  // if (a[i] % b[i]) != 0 is true and MakeFloat<T> has at least
  // kOrigLaneSize*8 + 1 mantissa bits (including the implied one bit), even in
  // the case where the magnitude of an inexact floating point division result
  // is rounded up.

#if (HWY_TARGET == HWY_NEON || HWY_TARGET == HWY_NEON_WITHOUT_AES) && \
    !HWY_HAVE_FLOAT64
  // On Armv7, do division by multiplying by the ApproximateReciprocal
  // to avoid unnecessary overhead as F32 Div refines the approximate
  // reciprocal using 4 Newton-Raphson iterations

  const RebindToSigned<decltype(d)> di;
  const RebindToUnsigned<decltype(d)> du;

  const auto flt_b = ConvertTo(df, b);
  auto flt_recip_b = ApproximateReciprocal(flt_b);
  if (kOrigLaneSize > 1) {
    flt_recip_b =
        Mul(flt_recip_b, ReciprocalNewtonRaphsonStep(flt_recip_b, flt_b));
  }

  auto q0 = ConvertTo(d, Mul(ConvertTo(df, a), flt_recip_b));
  const auto r0 = BitCast(di, hwy::HWY_NAMESPACE::NegMulAdd(q0, b, a));

  auto r1 = r0;

  // Need to negate r1[i] if a[i] < 0 is true
  if (IsSigned<TFromV<V>>()) {
    r1 = IfNegativeThenNegOrUndefIfZero(BitCast(di, a), r1);
  }

  // r1[i] is now equal to (a[i] < 0) ? (-r0[i]) : r0[i]

  auto abs_b = BitCast(du, b);
  if (IsSigned<TFromV<V>>()) {
    abs_b = BitCast(du, Abs(BitCast(di, abs_b)));
  }

  // If (r1[i] < 0 || r1[i] >= abs_b[i]) is true, then set q1[i] to -1.
  // Otherwise, set q1[i] to 0.

  // (r1[i] < 0 || r1[i] >= abs_b[i]) can be carried out using a single unsigned
  // comparison as static_cast<TU>(r1[i]) >= TU(LimitsMax<TI>() + 1) >= abs_b[i]
  // will be true if r1[i] < 0 is true.
  auto q1 = BitCast(di, VecFromMask(du, Ge(BitCast(du, r1), abs_b)));

  // q1[i] is now equal to (r1[i] < 0 || r1[i] >= abs_b[i]) ? -1 : 0

  // Need to negate q1[i] if r0[i] and b[i] do not have the same sign
  auto q1_negate_mask = r0;
  if (IsSigned<TFromV<V>>()) {
    q1_negate_mask = Xor(q1_negate_mask, BitCast(di, b));
  }
  q1 = IfNegativeThenElse(q1_negate_mask, Neg(q1), q1);

  // q1[i] is now equal to (r1[i] < 0 || r1[i] >= abs_b[i]) ?
  //                       (((r0[i] ^ b[i]) < 0) ? 1 : -1)

  // Need to subtract q1[i] from q0[i] to get the final result
  return Sub(q0, BitCast(d, q1));
#else
  // On targets other than Armv7 NEON, use F16 or F32 division as most targets
  // other than Armv7 NEON have native F32 divide instructions
  return ConvertTo(d, Div(ConvertTo(df, a), ConvertTo(df, b)));
#endif
}

template <size_t kOrigLaneSize, class V, HWY_IF_NOT_FLOAT_NOR_SPECIAL_V(V),
          HWY_IF_T_SIZE(TFromV<V>, kOrigLaneSize),
          HWY_IF_T_SIZE_ONE_OF_V(V, (1 << 4) | (1 << 8))>
HWY_INLINE V IntDivUsingFloatDiv(V a, V b) {
  // If kOrigLaneSize == sizeof(T) is true, at least two reciprocal
  // multiplication steps are needed as the mantissa of MakeFloat<T> has fewer
  // than kOrigLaneSize*8 + 1 bits

  using T = TFromV<V>;

#if HWY_HAVE_FLOAT64
  using TF = MakeFloat<T>;
#else
  using TF = float;
#endif

  const DFromV<decltype(a)> d;
  const RebindToSigned<decltype(d)> di;
  const RebindToUnsigned<decltype(d)> du;
  const Rebind<TF, decltype(d)> df;

  if (!IsSigned<T>()) {
    // If T is unsigned, set a[i] to (a[i] >= b[i] ? 1 : 0) and set b[i] to 1 if
    // b[i] > LimitsMax<MakeSigned<T>>() is true

    const auto one = Set(di, MakeSigned<T>{1});
    a = BitCast(
        d, IfNegativeThenElse(BitCast(di, b),
                              IfThenElseZero(RebindMask(di, Ge(a, b)), one),
                              BitCast(di, a)));
    b = BitCast(d, IfNegativeThenElse(BitCast(di, b), one, BitCast(di, b)));
  }

  // LimitsMin<T>() <= b[i] <= LimitsMax<MakeSigned<T>>() is now true

  const auto flt_b = IntDivConvIntToFloat(df, b);

#if (HWY_TARGET == HWY_NEON || HWY_TARGET == HWY_NEON_WITHOUT_AES) && \
    !HWY_HAVE_FLOAT64
  auto flt_recip_b = ApproximateReciprocal(flt_b);
  flt_recip_b =
      Mul(flt_recip_b, ReciprocalNewtonRaphsonStep(flt_recip_b, flt_b));
#else
  const auto flt_recip_b = Div(Set(df, TF(1.0)), flt_b);
#endif

  auto q0 =
      IntDivConvFloatToInt(d, Mul(IntDivConvIntToFloat(df, a), flt_recip_b));
  const auto r0 = BitCast(di, hwy::HWY_NAMESPACE::NegMulAdd(q0, b, a));

  auto q1 =
      IntDivConvFloatToInt(di, Mul(IntDivConvIntToFloat(df, r0), flt_recip_b));
  const auto r1 = hwy::HWY_NAMESPACE::NegMulAdd(q1, BitCast(di, b), r0);

  auto r3 = r1;

#if !HWY_HAVE_FLOAT64
  // Need two additional reciprocal multiplication steps for I64/U64 vectors if
  // HWY_HAVE_FLOAT64 is 0
  if (sizeof(T) == 8) {
    const auto q2 = IntDivConvFloatToInt(
        di, Mul(IntDivConvIntToFloat(df, r1), flt_recip_b));
    const auto r2 = hwy::HWY_NAMESPACE::NegMulAdd(q2, BitCast(di, b), r1);

    const auto q3 = IntDivConvFloatToInt(
        di, Mul(IntDivConvIntToFloat(df, r2), flt_recip_b));
    r3 = hwy::HWY_NAMESPACE::NegMulAdd(q3, BitCast(di, b), r2);

    q0 = Add(q0, BitCast(d, q2));
    q1 = Add(q1, q3);
  }
#endif  // !HWY_HAVE_FLOAT64

  auto r4 = r3;

  // Need to negate r4[i] if a[i] < 0 is true
  if (IsSigned<TFromV<V>>()) {
    r4 = IfNegativeThenNegOrUndefIfZero(BitCast(di, a), r4);
  }

  // r4[i] is now equal to (a[i] < 0) ? (-r3[i]) : r3[i]

  auto abs_b = BitCast(du, b);
  if (IsSigned<TFromV<V>>()) {
    abs_b = BitCast(du, Abs(BitCast(di, abs_b)));
  }

  // If (r4[i] < 0 || r4[i] >= abs_b[i]) is true, then set q4[i] to -1.
  // Otherwise, set r4[i] to 0.

  // (r4[i] < 0 || r4[i] >= abs_b[i]) can be carried out using a single unsigned
  // comparison as static_cast<TU>(r4[i]) >= TU(LimitsMax<TI>() + 1) >= abs_b[i]
  // will be true if r4[i] < 0 is true.
  auto q4 = BitCast(di, VecFromMask(du, Ge(BitCast(du, r4), abs_b)));

  // q4[i] is now equal to (r4[i] < 0 || r4[i] >= abs_b[i]) ? -1 : 0

  // Need to negate q4[i] if r3[i] and b[i] do not have the same sign
  auto q4_negate_mask = r3;
  if (IsSigned<TFromV<V>>()) {
    q4_negate_mask = Xor(q4_negate_mask, BitCast(di, b));
  }
  q4 = IfNegativeThenElse(q4_negate_mask, Neg(q4), q4);

  // q4[i] is now equal to (r4[i] < 0 || r4[i] >= abs_b[i]) ?
  //                       (((r3[i] ^ b[i]) < 0) ? 1 : -1)

  // The final result is equal to q0[i] + q1[i] - q4[i]
  return Sub(Add(q0, BitCast(d, q1)), BitCast(d, q4));
}

template <size_t kOrigLaneSize, class V,
          HWY_IF_T_SIZE_ONE_OF_V(V, (1 << 1) | (1 << 2)),
          HWY_IF_V_SIZE_LE_V(
              V, HWY_MAX_BYTES /
                     ((!HWY_HAVE_FLOAT16 && sizeof(TFromV<V>) == 1) ? 4 : 2))>
HWY_INLINE V IntDiv(V a, V b) {
  using T = TFromV<V>;

  // If HWY_HAVE_FLOAT16 is 0, need to promote I8 to I32 and U8 to U32
  using TW = MakeWide<
      If<(!HWY_HAVE_FLOAT16 && sizeof(TFromV<V>) == 1), MakeWide<T>, T>>;

  const DFromV<decltype(a)> d;
  const Rebind<TW, decltype(d)> dw;

#if HWY_TARGET <= HWY_SSE2
  // On SSE2/SSSE3/SSE4/AVX2/AVX3, promote to and from MakeSigned<TW> to avoid
  // unnecessary overhead
  const RebindToSigned<decltype(dw)> dw_i;

  // On SSE2/SSSE3/SSE4/AVX2/AVX3, demote to MakeSigned<T> if
  // kOrigLaneSize < sizeof(T) to avoid unnecessary overhead
  const If<(kOrigLaneSize < sizeof(T)), RebindToSigned<decltype(d)>,
           decltype(d)>
      d_demote_to;
#else
  // On other targets, promote to TW and demote to T
  const decltype(dw) dw_i;
  const decltype(d) d_demote_to;
#endif

  return BitCast(
      d, DemoteTo(d_demote_to, IntDivUsingFloatDiv<kOrigLaneSize>(
                                   PromoteTo(dw_i, a), PromoteTo(dw_i, b))));
}

template <size_t kOrigLaneSize, class V,
          HWY_IF_T_SIZE_ONE_OF_V(V,
                                 (HWY_HAVE_FLOAT16 ? (1 << 1) : 0) | (1 << 2)),
          HWY_IF_V_SIZE_GT_V(V, HWY_MAX_BYTES / 2)>
HWY_INLINE V IntDiv(V a, V b) {
  const DFromV<decltype(a)> d;
  const RepartitionToWide<decltype(d)> dw;

#if HWY_TARGET <= HWY_SSE2
  // On SSE2/SSSE3/SSE4/AVX2/AVX3, promote to and from MakeSigned<TW> to avoid
  // unnecessary overhead
  const RebindToSigned<decltype(dw)> dw_i;

  // On SSE2/SSSE3/SSE4/AVX2/AVX3, demote to MakeSigned<TFromV<V>> if
  // kOrigLaneSize < sizeof(TFromV<V>) to avoid unnecessary overhead
  const If<(kOrigLaneSize < sizeof(TFromV<V>)), RebindToSigned<decltype(d)>,
           decltype(d)>
      d_demote_to;
#else
  // On other targets, promote to MakeWide<TFromV<V>> and demote to TFromV<V>
  const decltype(dw) dw_i;
  const decltype(d) d_demote_to;
#endif

  return BitCast(d, OrderedDemote2To(
                        d_demote_to,
                        IntDivUsingFloatDiv<kOrigLaneSize>(
                            PromoteLowerTo(dw_i, a), PromoteLowerTo(dw_i, b)),
                        IntDivUsingFloatDiv<kOrigLaneSize>(
                            PromoteUpperTo(dw_i, a), PromoteUpperTo(dw_i, b))));
}

#if !HWY_HAVE_FLOAT16
template <size_t kOrigLaneSize, class V, HWY_IF_UI8(TFromV<V>),
          HWY_IF_V_SIZE_V(V, HWY_MAX_BYTES / 2)>
HWY_INLINE V IntDiv(V a, V b) {
  const DFromV<decltype(a)> d;
  const Rebind<MakeWide<TFromV<V>>, decltype(d)> dw;

#if HWY_TARGET <= HWY_SSE2
  // On SSE2/SSSE3, demote from int16_t to TFromV<V> to avoid unnecessary
  // overhead
  const RebindToSigned<decltype(dw)> dw_i;
#else
  // On other targets, demote from MakeWide<TFromV<V>> to TFromV<V>
  const decltype(dw) dw_i;
#endif

  return DemoteTo(d,
                  BitCast(dw_i, IntDiv<1>(PromoteTo(dw, a), PromoteTo(dw, b))));
}
template <size_t kOrigLaneSize, class V, HWY_IF_UI8(TFromV<V>),
          HWY_IF_V_SIZE_GT_V(V, HWY_MAX_BYTES / 2)>
HWY_INLINE V IntDiv(V a, V b) {
  const DFromV<decltype(a)> d;
  const RepartitionToWide<decltype(d)> dw;

#if HWY_TARGET <= HWY_SSE2
  // On SSE2/SSSE3, demote from int16_t to TFromV<V> to avoid unnecessary
  // overhead
  const RebindToSigned<decltype(dw)> dw_i;
#else
  // On other targets, demote from MakeWide<TFromV<V>> to TFromV<V>
  const decltype(dw) dw_i;
#endif

  return OrderedDemote2To(
      d, BitCast(dw_i, IntDiv<1>(PromoteLowerTo(dw, a), PromoteLowerTo(dw, b))),
      BitCast(dw_i, IntDiv<1>(PromoteUpperTo(dw, a), PromoteUpperTo(dw, b))));
}
#endif  // !HWY_HAVE_FLOAT16

template <size_t kOrigLaneSize, class V,
          HWY_IF_T_SIZE_ONE_OF_V(V,
                                 (HWY_HAVE_FLOAT64 ? 0 : (1 << 4)) | (1 << 8))>
HWY_INLINE V IntDiv(V a, V b) {
  return IntDivUsingFloatDiv<kOrigLaneSize>(a, b);
}

#if HWY_HAVE_FLOAT64
template <size_t kOrigLaneSize, class V, HWY_IF_UI32(TFromV<V>),
          HWY_IF_V_SIZE_LE_V(V, HWY_MAX_BYTES / 2)>
HWY_INLINE V IntDiv(V a, V b) {
  const DFromV<decltype(a)> d;
  const Rebind<double, decltype(d)> df64;

  return DemoteTo(d, Div(PromoteTo(df64, a), PromoteTo(df64, b)));
}
template <size_t kOrigLaneSize, class V, HWY_IF_UI32(TFromV<V>),
          HWY_IF_V_SIZE_GT_V(V, HWY_MAX_BYTES / 2)>
HWY_INLINE V IntDiv(V a, V b) {
  const DFromV<decltype(a)> d;
  const Half<decltype(d)> dh;
  const Repartition<double, decltype(d)> df64;

  return Combine(
      d, DemoteTo(dh, Div(PromoteUpperTo(df64, a), PromoteUpperTo(df64, b))),
      DemoteTo(dh, Div(PromoteLowerTo(df64, a), PromoteLowerTo(df64, b))));
}
#endif  // HWY_HAVE_FLOAT64

template <size_t kOrigLaneSize, class V, HWY_IF_NOT_FLOAT_NOR_SPECIAL_V(V),
          HWY_IF_T_SIZE_ONE_OF_V(V, ((HWY_TARGET <= HWY_SSE2 ||
                                      HWY_TARGET == HWY_WASM ||
                                      HWY_TARGET == HWY_WASM_EMU256)
                                         ? 0
                                         : (1 << 1)) |
                                        (1 << 2) | (1 << 4) | (1 << 8))>
HWY_INLINE V IntMod(V a, V b) {
  return hwy::HWY_NAMESPACE::NegMulAdd(IntDiv<kOrigLaneSize>(a, b), b, a);
}

#if HWY_TARGET <= HWY_SSE2 || HWY_TARGET == HWY_WASM || \
    HWY_TARGET == HWY_WASM_EMU256
template <size_t kOrigLaneSize, class V, HWY_IF_UI8(TFromV<V>),
          HWY_IF_V_SIZE_LE_V(V, HWY_MAX_BYTES / 2)>
HWY_INLINE V IntMod(V a, V b) {
  const DFromV<decltype(a)> d;
  const Rebind<MakeWide<TFromV<V>>, decltype(d)> dw;
  return DemoteTo(d, IntMod<kOrigLaneSize>(PromoteTo(dw, a), PromoteTo(dw, b)));
}

template <size_t kOrigLaneSize, class V, HWY_IF_UI8(TFromV<V>),
          HWY_IF_V_SIZE_GT_V(V, HWY_MAX_BYTES / 2)>
HWY_INLINE V IntMod(V a, V b) {
  const DFromV<decltype(a)> d;
  const RepartitionToWide<decltype(d)> dw;
  return OrderedDemote2To(
      d, IntMod<kOrigLaneSize>(PromoteLowerTo(dw, a), PromoteLowerTo(dw, b)),
      IntMod<kOrigLaneSize>(PromoteUpperTo(dw, a), PromoteUpperTo(dw, b)));
}
#endif  // HWY_TARGET <= HWY_SSE2 || HWY_TARGET == HWY_WASM || HWY_TARGET ==
        // HWY_WASM_EMU256

}  // namespace detail

#if HWY_TARGET == HWY_SCALAR

template <class T, HWY_IF_NOT_FLOAT_NOR_SPECIAL(T)>
HWY_API Vec1<T> operator/(Vec1<T> a, Vec1<T> b) {
  return detail::IntDiv<sizeof(T)>(a, b);
}
template <class T, HWY_IF_NOT_FLOAT_NOR_SPECIAL(T)>
HWY_API Vec1<T> operator%(Vec1<T> a, Vec1<T> b) {
  return detail::IntMod<sizeof(T)>(a, b);
}

#else  // HWY_TARGET != HWY_SCALAR

template <class T, size_t N, HWY_IF_NOT_FLOAT_NOR_SPECIAL(T)>
HWY_API Vec128<T, N> operator/(Vec128<T, N> a, Vec128<T, N> b) {
  return detail::IntDiv<sizeof(T)>(a, b);
}

template <class T, size_t N, HWY_IF_NOT_FLOAT_NOR_SPECIAL(T)>
HWY_API Vec128<T, N> operator%(Vec128<T, N> a, Vec128<T, N> b) {
  return detail::IntMod<sizeof(T)>(a, b);
}

#if HWY_CAP_GE256
template <class T, HWY_IF_NOT_FLOAT_NOR_SPECIAL(T)>
HWY_API Vec256<T> operator/(Vec256<T> a, Vec256<T> b) {
  return detail::IntDiv<sizeof(T)>(a, b);
}
template <class T, HWY_IF_NOT_FLOAT_NOR_SPECIAL(T)>
HWY_API Vec256<T> operator%(Vec256<T> a, Vec256<T> b) {
  return detail::IntMod<sizeof(T)>(a, b);
}
#endif

#if HWY_CAP_GE512
template <class T, HWY_IF_NOT_FLOAT_NOR_SPECIAL(T)>
HWY_API Vec512<T> operator/(Vec512<T> a, Vec512<T> b) {
  return detail::IntDiv<sizeof(T)>(a, b);
}
template <class T, HWY_IF_NOT_FLOAT_NOR_SPECIAL(T)>
HWY_API Vec512<T> operator%(Vec512<T> a, Vec512<T> b) {
  return detail::IntMod<sizeof(T)>(a, b);
}
#endif

#endif  // HWY_TARGET == HWY_SCALAR

#endif  // HWY_NATIVE_INT_DIV

// ------------------------------ SatWidenMulPairwiseAdd

#if (defined(HWY_NATIVE_U8_I8_SATWIDENMULPAIRWISEADD) == \
     defined(HWY_TARGET_TOGGLE))

#ifdef HWY_NATIVE_U8_I8_SATWIDENMULPAIRWISEADD
#undef HWY_NATIVE_U8_I8_SATWIDENMULPAIRWISEADD
#else
#define HWY_NATIVE_U8_I8_SATWIDENMULPAIRWISEADD
#endif

template <class DI16, class VU8, class VI8,
          class VU8_2 = Vec<Repartition<uint8_t, DI16>>, HWY_IF_I16_D(DI16),
          HWY_IF_U8_D(DFromV<VU8>), HWY_IF_I8_D(DFromV<VI8>),
          HWY_IF_LANES_D(DFromV<VU8>, HWY_MAX_LANES_V(VI8)),
          HWY_IF_LANES_D(DFromV<VU8>, HWY_MAX_LANES_V(VU8_2))>
HWY_API Vec<DI16> SatWidenMulPairwiseAdd(DI16 di16, VU8 a, VI8 b) {
  const RebindToUnsigned<decltype(di16)> du16;

  const auto a0 = BitCast(di16, PromoteEvenTo(du16, a));
  const auto b0 = PromoteEvenTo(di16, b);

  const auto a1 = BitCast(di16, PromoteOddTo(du16, a));
  const auto b1 = PromoteOddTo(di16, b);

  return SaturatedAdd(Mul(a0, b0), Mul(a1, b1));
}

#endif

// ------------------------------ SumOfMulQuadAccumulate

#if (defined(HWY_NATIVE_I8_I8_SUMOFMULQUADACCUMULATE) == \
     defined(HWY_TARGET_TOGGLE))

#ifdef HWY_NATIVE_I8_I8_SUMOFMULQUADACCUMULATE
#undef HWY_NATIVE_I8_I8_SUMOFMULQUADACCUMULATE
#else
#define HWY_NATIVE_I8_I8_SUMOFMULQUADACCUMULATE
#endif

template <class DI32, HWY_IF_I32_D(DI32)>
HWY_API VFromD<DI32> SumOfMulQuadAccumulate(DI32 di32,
                                            VFromD<Repartition<int8_t, DI32>> a,
                                            VFromD<Repartition<int8_t, DI32>> b,
                                            VFromD<DI32> sum) {
  const Repartition<int16_t, decltype(di32)> di16;

  const auto a0 = PromoteEvenTo(di16, a);
  const auto b0 = PromoteEvenTo(di16, b);

  const auto a1 = PromoteOddTo(di16, a);
  const auto b1 = PromoteOddTo(di16, b);

  return Add(sum, Add(WidenMulPairwiseAdd(di32, a0, b0),
                      WidenMulPairwiseAdd(di32, a1, b1)));
}

#endif

#if (defined(HWY_NATIVE_U8_U8_SUMOFMULQUADACCUMULATE) == \
     defined(HWY_TARGET_TOGGLE))

#ifdef HWY_NATIVE_U8_U8_SUMOFMULQUADACCUMULATE
#undef HWY_NATIVE_U8_U8_SUMOFMULQUADACCUMULATE
#else
#define HWY_NATIVE_U8_U8_SUMOFMULQUADACCUMULATE
#endif

template <class DU32, HWY_IF_U32_D(DU32)>
HWY_API VFromD<DU32> SumOfMulQuadAccumulate(
    DU32 du32, VFromD<Repartition<uint8_t, DU32>> a,
    VFromD<Repartition<uint8_t, DU32>> b, VFromD<DU32> sum) {
  const Repartition<uint16_t, decltype(du32)> du16;
  const RebindToSigned<decltype(du16)> di16;
  const RebindToSigned<decltype(du32)> di32;

  const auto lo8_mask = Set(di16, int16_t{0x00FF});
  const auto a0 = And(BitCast(di16, a), lo8_mask);
  const auto b0 = And(BitCast(di16, b), lo8_mask);

  const auto a1 = BitCast(di16, ShiftRight<8>(BitCast(du16, a)));
  const auto b1 = BitCast(di16, ShiftRight<8>(BitCast(du16, b)));

  return Add(sum, Add(BitCast(du32, WidenMulPairwiseAdd(di32, a0, b0)),
                      BitCast(du32, WidenMulPairwiseAdd(di32, a1, b1))));
}

#endif

#if (defined(HWY_NATIVE_U8_I8_SUMOFMULQUADACCUMULATE) == \
     defined(HWY_TARGET_TOGGLE))

#ifdef HWY_NATIVE_U8_I8_SUMOFMULQUADACCUMULATE
#undef HWY_NATIVE_U8_I8_SUMOFMULQUADACCUMULATE
#else
#define HWY_NATIVE_U8_I8_SUMOFMULQUADACCUMULATE
#endif

template <class DI32, HWY_IF_I32_D(DI32)>
HWY_API VFromD<DI32> SumOfMulQuadAccumulate(
    DI32 di32, VFromD<Repartition<uint8_t, DI32>> a_u,
    VFromD<Repartition<int8_t, DI32>> b_i, VFromD<DI32> sum) {
  const Repartition<int16_t, decltype(di32)> di16;
  const RebindToUnsigned<decltype(di16)> du16;

  const auto a0 = And(BitCast(di16, a_u), Set(di16, int16_t{0x00FF}));
  const auto b0 = ShiftRight<8>(ShiftLeft<8>(BitCast(di16, b_i)));

  const auto a1 = BitCast(di16, ShiftRight<8>(BitCast(du16, a_u)));
  const auto b1 = ShiftRight<8>(BitCast(di16, b_i));

  // NOTE: SatWidenMulPairwiseAdd(di16, a_u, b_i) cannot be used in
  // SumOfMulQuadAccumulate as it is possible for
  // a_u[0]*b_i[0]+a_u[1]*b_i[1] to overflow an int16_t if a_u[0], b_i[0],
  // a_u[1], and b_i[1] are all non-zero and b_i[0] and b_i[1] have the same
  // sign.

  return Add(sum, Add(WidenMulPairwiseAdd(di32, a0, b0),
                      WidenMulPairwiseAdd(di32, a1, b1)));
}

#endif

#if (defined(HWY_NATIVE_I16_I16_SUMOFMULQUADACCUMULATE) == \
     defined(HWY_TARGET_TOGGLE))

#ifdef HWY_NATIVE_I16_I16_SUMOFMULQUADACCUMULATE
#undef HWY_NATIVE_I16_I16_SUMOFMULQUADACCUMULATE
#else
#define HWY_NATIVE_I16_I16_SUMOFMULQUADACCUMULATE
#endif

#if HWY_HAVE_INTEGER64
template <class DI64, HWY_IF_I64_D(DI64)>
HWY_API VFromD<DI64> SumOfMulQuadAccumulate(
    DI64 di64, VFromD<Repartition<int16_t, DI64>> a,
    VFromD<Repartition<int16_t, DI64>> b, VFromD<DI64> sum) {
  const Repartition<int32_t, decltype(di64)> di32;

  // WidenMulPairwiseAdd(di32, a, b) is okay here as
  // a[0]*b[0]+a[1]*b[1] is between -2147418112 and 2147483648 and as
  // a[0]*b[0]+a[1]*b[1] can only overflow an int32_t if
  // a[0], b[0], a[1], and b[1] are all equal to -32768.

  const auto i32_pairwise_sum = WidenMulPairwiseAdd(di32, a, b);
  const auto i32_pairwise_sum_overflow =
      VecFromMask(di32, Eq(i32_pairwise_sum, Set(di32, LimitsMin<int32_t>())));

  // The upper 32 bits of sum0 and sum1 need to be zeroed out in the case of
  // overflow.
  const auto hi32_mask = Set(di64, static_cast<int64_t>(~int64_t{0xFFFFFFFF}));
  const auto p0_zero_out_mask =
      ShiftLeft<32>(BitCast(di64, i32_pairwise_sum_overflow));
  const auto p1_zero_out_mask =
      And(BitCast(di64, i32_pairwise_sum_overflow), hi32_mask);

  const auto p0 =
      AndNot(p0_zero_out_mask,
             ShiftRight<32>(ShiftLeft<32>(BitCast(di64, i32_pairwise_sum))));
  const auto p1 =
      AndNot(p1_zero_out_mask, ShiftRight<32>(BitCast(di64, i32_pairwise_sum)));

  return Add(sum, Add(p0, p1));
}
#endif  // HWY_HAVE_INTEGER64
#endif  // HWY_NATIVE_I16_I16_SUMOFMULQUADACCUMULATE

#if (defined(HWY_NATIVE_U16_U16_SUMOFMULQUADACCUMULATE) == \
     defined(HWY_TARGET_TOGGLE))

#ifdef HWY_NATIVE_U16_U16_SUMOFMULQUADACCUMULATE
#undef HWY_NATIVE_U16_U16_SUMOFMULQUADACCUMULATE
#else
#define HWY_NATIVE_U16_U16_SUMOFMULQUADACCUMULATE
#endif

#if HWY_HAVE_INTEGER64
template <class DU64, HWY_IF_U64_D(DU64)>
HWY_API VFromD<DU64> SumOfMulQuadAccumulate(
    DU64 du64, VFromD<Repartition<uint16_t, DU64>> a,
    VFromD<Repartition<uint16_t, DU64>> b, VFromD<DU64> sum) {
  const auto u32_even_prod = MulEven(a, b);
  const auto u32_odd_prod = MulOdd(a, b);

  const auto p0 = Add(PromoteEvenTo(du64, u32_even_prod),
                      PromoteEvenTo(du64, u32_odd_prod));
  const auto p1 =
      Add(PromoteOddTo(du64, u32_even_prod), PromoteOddTo(du64, u32_odd_prod));

  return Add(sum, Add(p0, p1));
}
#endif  // HWY_HAVE_INTEGER64
#endif  // HWY_NATIVE_U16_U16_SUMOFMULQUADACCUMULATE

// ------------------------------ F64 ApproximateReciprocal

#if (defined(HWY_NATIVE_F64_APPROX_RECIP) == defined(HWY_TARGET_TOGGLE))
#ifdef HWY_NATIVE_F64_APPROX_RECIP
#undef HWY_NATIVE_F64_APPROX_RECIP
#else
#define HWY_NATIVE_F64_APPROX_RECIP
#endif

#if HWY_HAVE_FLOAT64
template <class V, HWY_IF_F64_D(DFromV<V>)>
HWY_API V ApproximateReciprocal(V v) {
  const DFromV<decltype(v)> d;
  return Div(Set(d, 1.0), v);
}
#endif  // HWY_HAVE_FLOAT64

#endif  // HWY_NATIVE_F64_APPROX_RECIP

// ------------------------------ F64 ApproximateReciprocalSqrt

#if (defined(HWY_NATIVE_F64_APPROX_RSQRT) == defined(HWY_TARGET_TOGGLE))
#ifdef HWY_NATIVE_F64_APPROX_RSQRT
#undef HWY_NATIVE_F64_APPROX_RSQRT
#else
#define HWY_NATIVE_F64_APPROX_RSQRT
#endif

#if HWY_HAVE_FLOAT64
template <class V, HWY_IF_F64_D(DFromV<V>)>
HWY_API V ApproximateReciprocalSqrt(V v) {
  const DFromV<decltype(v)> d;
  const RebindToUnsigned<decltype(d)> du;
  const auto half = Mul(v, Set(d, 0.5));
  // Initial guess based on log2(f)
  const auto guess = BitCast(d, Sub(Set(du, uint64_t{0x5FE6EB50C7B537A9u}),
                                    ShiftRight<1>(BitCast(du, v))));
  // One Newton-Raphson iteration
  return Mul(guess, NegMulAdd(Mul(half, guess), guess, Set(d, 1.5)));
}
#endif  // HWY_HAVE_FLOAT64

#endif  // HWY_NATIVE_F64_APPROX_RSQRT

// ------------------------------ Compress*

#if (defined(HWY_NATIVE_COMPRESS8) == defined(HWY_TARGET_TOGGLE))
#ifdef HWY_NATIVE_COMPRESS8
#undef HWY_NATIVE_COMPRESS8
#else
#define HWY_NATIVE_COMPRESS8
#endif

template <class V, class D, typename T, HWY_IF_T_SIZE(T, 1)>
HWY_API size_t CompressBitsStore(V v, const uint8_t* HWY_RESTRICT bits, D d,
                                 T* unaligned) {
  HWY_ALIGN T lanes[MaxLanes(d)];
  Store(v, d, lanes);

  const Simd<T, HWY_MIN(MaxLanes(d), 8), 0> d8;
  T* HWY_RESTRICT pos = unaligned;

  HWY_ALIGN constexpr T table[2048] = {
      01234567/**/ 0, 1, 2, 3, 4, 5, 6, 7,  //
      10234567/**/ 0, 1, 2, 3, 4, 5, 6, 7,  //
      20134567/**/ 0, 2, 1, 3, 4, 5, 6, 7,  //
      12034567/**/ 0, 1, 2, 3, 4, 5, 6, 7,  //
      30124567/**/ 0, 3, 1, 2, 4, 5, 6, 7,  //
      13024567/**/ 0, 1, 3, 2, 4, 5, 6, 7,  //
      23014567/**/ 0, 2, 3, 1, 4, 5, 6, 7,  //
      12304567/**/ 0, 1, 2, 3, 4, 5, 6, 7,  //
      40123567/**/ 0, 4, 1, 2, 3, 5, 6, 7,  //
      14023567/**/ 0, 1, 4, 2, 3, 5, 6, 7,  //
      24013567/**/ 0, 2, 4, 1, 3, 5, 6, 7,  //
      12403567/**/ 0, 1, 2, 4, 3, 5, 6, 7,  //
      34012567/**/ 0, 3, 4, 1, 2, 5, 6, 7,  //
      13402567/**/ 0, 1, 3, 4, 2, 5, 6, 7,  //
      23401567/**/ 0, 2, 3, 4, 1, 5, 6, 7,  //
      12340567/**/ 0, 1, 2, 3, 4, 5, 6, 7,  //
      50123467/**/ 0, 5, 1, 2, 3, 4, 6, 7,  //
      15023467/**/ 0, 1, 5, 2, 3, 4, 6, 7,  //
      25013467/**/ 0, 2, 5, 1, 3, 4, 6, 7,  //
      12503467/**/ 0, 1, 2, 5, 3, 4, 6, 7,  //
      35012467/**/ 0, 3, 5, 1, 2, 4, 6, 7,  //
      13502467/**/ 0, 1, 3, 5, 2, 4, 6, 7,  //
      23501467/**/ 0, 2, 3, 5, 1, 4, 6, 7,  //
      12350467/**/ 0, 1, 2, 3, 5, 4, 6, 7,  //
      45012367/**/ 0, 4, 5, 1, 2, 3, 6, 7,  //
      14502367/**/ 0, 1, 4, 5, 2, 3, 6, 7,  //
      24501367/**/ 0, 2, 4, 5, 1, 3, 6, 7,  //
      12450367/**/ 0, 1, 2, 4, 5, 3, 6, 7,  //
      34501267/**/ 0, 3, 4, 5, 1, 2, 6, 7,  //
      13450267/**/ 0, 1, 3, 4, 5, 2, 6, 7,  //
      23450167/**/ 0, 2, 3, 4, 5, 1, 6, 7,  //
      12345067/**/ 0, 1, 2, 3, 4, 5, 6, 7,  //
      60123457/**/ 0, 6, 1, 2, 3, 4, 5, 7,  //
      16023457/**/ 0, 1, 6, 2, 3, 4, 5, 7,  //
      26013457/**/ 0, 2, 6, 1, 3, 4, 5, 7,  //
      12603457/**/ 0, 1, 2, 6, 3, 4, 5, 7,  //
      36012457/**/ 0, 3, 6, 1, 2, 4, 5, 7,  //
      13602457/**/ 0, 1, 3, 6, 2, 4, 5, 7,  //
      23601457/**/ 0, 2, 3, 6, 1, 4, 5, 7,  //
      12360457/**/ 0, 1, 2, 3, 6, 4, 5, 7,  //
      46012357/**/ 0, 4, 6, 1, 2, 3, 5, 7,  //
      14602357/**/ 0, 1, 4, 6, 2, 3, 5, 7,  //
      24601357/**/ 0, 2, 4, 6, 1, 3, 5, 7,  //
      12460357/**/ 0, 1, 2, 4, 6, 3, 5, 7,  //
      34601257/**/ 0, 3, 4, 6, 1, 2, 5, 7,  //
      13460257/**/ 0, 1, 3, 4, 6, 2, 5, 7,  //
      23460157/**/ 0, 2, 3, 4, 6, 1, 5, 7,  //
      12346057/**/ 0, 1, 2, 3, 4, 6, 5, 7,  //
      56012347/**/ 0, 5, 6, 1, 2, 3, 4, 7,  //
      15602347/**/ 0, 1, 5, 6, 2, 3, 4, 7,  //
      25601347/**/ 0, 2, 5, 6, 1, 3, 4, 7,  //
      12560347/**/ 0, 1, 2, 5, 6, 3, 4, 7,  //
      35601247/**/ 0, 3, 5, 6, 1, 2, 4, 7,  //
      13560247/**/ 0, 1, 3, 5, 6, 2, 4, 7,  //
      23560147/**/ 0, 2, 3, 5, 6, 1, 4, 7,  //
      12356047/**/ 0, 1, 2, 3, 5, 6, 4, 7,  //
      45601237/**/ 0, 4, 5, 6, 1, 2, 3, 7,  //
      14560237/**/ 0, 1, 4, 5, 6, 2, 3, 7,  //
      24560137/**/ 0, 2, 4, 5, 6, 1, 3, 7,  //
      12456037/**/ 0, 1, 2, 4, 5, 6, 3, 7,  //
      34560127/**/ 0, 3, 4, 5, 6, 1, 2, 7,  //
      13456027/**/ 0, 1, 3, 4, 5, 6, 2, 7,  //
      23456017/**/ 0, 2, 3, 4, 5, 6, 1, 7,  //
      12345607/**/ 0, 1, 2, 3, 4, 5, 6, 7,  //
      70123456/**/ 0, 7, 1, 2, 3, 4, 5, 6,  //
      17023456/**/ 0, 1, 7, 2, 3, 4, 5, 6,  //
      27013456/**/ 0, 2, 7, 1, 3, 4, 5, 6,  //
      12703456/**/ 0, 1, 2, 7, 3, 4, 5, 6,  //
      37012456/**/ 0, 3, 7, 1, 2, 4, 5, 6,  //
      13702456/**/ 0, 1, 3, 7, 2, 4, 5, 6,  //
      23701456/**/ 0, 2, 3, 7, 1, 4, 5, 6,  //
      12370456/**/ 0, 1, 2, 3, 7, 4, 5, 6,  //
      47012356/**/ 0, 4, 7, 1, 2, 3, 5, 6,  //
      14702356/**/ 0, 1, 4, 7, 2, 3, 5, 6,  //
      24701356/**/ 0, 2, 4, 7, 1, 3, 5, 6,  //
      12470356/**/ 0, 1, 2, 4, 7, 3, 5, 6,  //
      34701256/**/ 0, 3, 4, 7, 1, 2, 5, 6,  //
      13470256/**/ 0, 1, 3, 4, 7, 2, 5, 6,  //
      23470156/**/ 0, 2, 3, 4, 7, 1, 5, 6,  //
      12347056/**/ 0, 1, 2, 3, 4, 7, 5, 6,  //
      57012346/**/ 0, 5, 7, 1, 2, 3, 4, 6,  //
      15702346/**/ 0, 1, 5, 7, 2, 3, 4, 6,  //
      25701346/**/ 0, 2, 5, 7, 1, 3, 4, 6,  //
      12570346/**/ 0, 1, 2, 5, 7, 3, 4, 6,  //
      35701246/**/ 0, 3, 5, 7, 1, 2, 4, 6,  //
      13570246/**/ 0, 1, 3, 5, 7, 2, 4, 6,  //
      23570146/**/ 0, 2, 3, 5, 7, 1, 4, 6,  //
      12357046/**/ 0, 1, 2, 3, 5, 7, 4, 6,  //
      45701236/**/ 0, 4, 5, 7, 1, 2, 3, 6,  //
      14570236/**/ 0, 1, 4, 5, 7, 2, 3, 6,  //
      24570136/**/ 0, 2, 4, 5, 7, 1, 3, 6,  //
      12457036/**/ 0, 1, 2, 4, 5, 7, 3, 6,  //
      34570126/**/ 0, 3, 4, 5, 7, 1, 2, 6,  //
      13457026/**/ 0, 1, 3, 4, 5, 7, 2, 6,  //
      23457016/**/ 0, 2, 3, 4, 5, 7, 1, 6,  //
      12345706/**/ 0, 1, 2, 3, 4, 5, 7, 6,  //
      67012345/**/ 0, 6, 7, 1, 2, 3, 4, 5,  //
      16702345/**/ 0, 1, 6, 7, 2, 3, 4, 5,  //
      26701345/**/ 0, 2, 6, 7, 1, 3, 4, 5,  //
      12670345/**/ 0, 1, 2, 6, 7, 3, 4, 5,  //
      36701245/**/ 0, 3, 6, 7, 1, 2, 4, 5,  //
      13670245/**/ 0, 1, 3, 6, 7, 2, 4, 5,  //
      23670145/**/ 0, 2, 3, 6, 7, 1, 4, 5,  //
      12367045/**/ 0, 1, 2, 3, 6, 7, 4, 5,  //
      46701235/**/ 0, 4, 6, 7, 1, 2, 3, 5,  //
      14670235/**/ 0, 1, 4, 6, 7, 2, 3, 5,  //
      24670135/**/ 0, 2, 4, 6, 7, 1, 3, 5,  //
      12467035/**/ 0, 1, 2, 4, 6, 7, 3, 5,  //
      34670125/**/ 0, 3, 4, 6, 7, 1, 2, 5,  //
      13467025/**/ 0, 1, 3, 4, 6, 7, 2, 5,  //
      23467015/**/ 0, 2, 3, 4, 6, 7, 1, 5,  //
      12346705/**/ 0, 1, 2, 3, 4, 6, 7, 5,  //
      56701234/**/ 0, 5, 6, 7, 1, 2, 3, 4,  //
      15670234/**/ 0, 1, 5, 6, 7, 2, 3, 4,  //
      25670134/**/ 0, 2, 5, 6, 7, 1, 3, 4,  //
      12567034/**/ 0, 1, 2, 5, 6, 7, 3, 4,  //
      35670124/**/ 0, 3, 5, 6, 7, 1, 2, 4,  //
      13567024/**/ 0, 1, 3, 5, 6, 7, 2, 4,  //
      23567014/**/ 0, 2, 3, 5, 6, 7, 1, 4,  //
      12356704/**/ 0, 1, 2, 3, 5, 6, 7, 4,  //
      45670123/**/ 0, 4, 5, 6, 7, 1, 2, 3,  //
      14567023/**/ 0, 1, 4, 5, 6, 7, 2, 3,  //
      24567013/**/ 0, 2, 4, 5, 6, 7, 1, 3,  //
      12456703/**/ 0, 1, 2, 4, 5, 6, 7, 3,  //
      34567012/**/ 0, 3, 4, 5, 6, 7, 1, 2,  //
      13456702/**/ 0, 1, 3, 4, 5, 6, 7, 2,  //
      23456701/**/ 0, 2, 3, 4, 5, 6, 7, 1,  //
      12345670/**/ 0, 1, 2, 3, 4, 5, 6, 7};

  for (size_t i = 0; i < Lanes(d); i += 8) {
    // Each byte worth of bits is the index of one of 256 8-byte ranges, and its
    // population count determines how far to advance the write position.
    const size_t bits8 = bits[i / 8];
    const auto indices = Load(d8, table + bits8 * 8);
    const auto compressed = TableLookupBytes(LoadU(d8, lanes + i), indices);
    StoreU(compressed, d8, pos);
    pos += PopCount(bits8);
  }
  return static_cast<size_t>(pos - unaligned);
}

template <class V, class M, class D, typename T, HWY_IF_T_SIZE(T, 1)>
HWY_API size_t CompressStore(V v, M mask, D d, T* HWY_RESTRICT unaligned) {
  uint8_t bits[HWY_MAX(size_t{8}, MaxLanes(d) / 8)];
  (void)StoreMaskBits(d, mask, bits);
  return CompressBitsStore(v, bits, d, unaligned);
}

template <class V, class M, class D, typename T, HWY_IF_T_SIZE(T, 1)>
HWY_API size_t CompressBlendedStore(V v, M mask, D d,
                                    T* HWY_RESTRICT unaligned) {
  HWY_ALIGN T buf[MaxLanes(d)];
  const size_t bytes = CompressStore(v, mask, d, buf);
  BlendedStore(Load(d, buf), FirstN(d, bytes), d, unaligned);
  return bytes;
}

// For reasons unknown, HWY_IF_T_SIZE_V is a compile error in SVE.
template <class V, class M, typename T = TFromV<V>, HWY_IF_T_SIZE(T, 1)>
HWY_API V Compress(V v, const M mask) {
  const DFromV<V> d;
  HWY_ALIGN T lanes[MaxLanes(d)];
  (void)CompressStore(v, mask, d, lanes);
  return Load(d, lanes);
}

template <class V, typename T = TFromV<V>, HWY_IF_T_SIZE(T, 1)>
HWY_API V CompressBits(V v, const uint8_t* HWY_RESTRICT bits) {
  const DFromV<V> d;
  HWY_ALIGN T lanes[MaxLanes(d)];
  (void)CompressBitsStore(v, bits, d, lanes);
  return Load(d, lanes);
}

template <class V, class M, typename T = TFromV<V>, HWY_IF_T_SIZE(T, 1)>
HWY_API V CompressNot(V v, M mask) {
  return Compress(v, Not(mask));
}

#endif  // HWY_NATIVE_COMPRESS8

// ------------------------------ Expand

// Note that this generic implementation assumes <= 128 bit fixed vectors;
// the SVE and RVV targets provide their own native implementations.
#if (defined(HWY_NATIVE_EXPAND) == defined(HWY_TARGET_TOGGLE)) || HWY_IDE
#ifdef HWY_NATIVE_EXPAND
#undef HWY_NATIVE_EXPAND
#else
#define HWY_NATIVE_EXPAND
#endif

namespace detail {

#if HWY_IDE
template <class M>
HWY_INLINE uint64_t BitsFromMask(M /* mask */) {
  return 0;
}
#endif  // HWY_IDE

template <size_t N>
HWY_INLINE Vec128<uint8_t, N> IndicesForExpandFromBits(uint64_t mask_bits) {
  static_assert(N <= 8"Should only be called for half-vectors");
  const Simd<uint8_t, N, 0> du8;
  HWY_DASSERT(mask_bits < 0x100);
  alignas(16static constexpr uint8_t table[2048] = {
      // PrintExpand8x8Tables
      128128128128128128128128,  //
      0,   128128128128128128128,  //
      1280,   128128128128128128,  //
      0,   1,   128128128128128128,  //
      1281280,   128128128128128,  //
      0,   1281,   128128128128128,  //
      1280,   1,   128128128128128,  //
      0,   1,   2,   128128128128128,  //
      1281281280,   128128128128,  //
      0,   1281281,   128128128128,  //
      1280,   1281,   128128128128,  //
      0,   1,   1282,   128128128128,  //
      1281280,   1,   128128128128,  //
      0,   1281,   2,   128128128128,  //
      1280,   1,   2,   128128128128,  //
      0,   1,   2,   3,   128128128128,  //
      1281281281280,   128128128,  //
      0,   1281281281,   128128128,  //
      1280,   1281281,   128128128,  //
      0,   1,   1281282,   128128128,  //
      1281280,   1281,   128128128,  //
      0,   1281,   1282,   128128128,  //
      1280,   1,   1282,   128128128,  //
      0,   1,   2,   1283,   128128128,  //
      1281281280,   1,   128128128,  //
      0,   1281281,   2,   128128128,  //
      1280,   1281,   2,   128128128,  //
      0,   1,   1282,   3,   128128128,  //
      1281280,   1,   2,   128128128,  //
      0,   1281,   2,   3,   128128128,  //
      1280,   1,   2,   3,   128128128,  //
      0,   1,   2,   3,   4,   128128128,  //
      1281281281281280,   128128,  //
      0,   1281281281281,   128128,  //
      1280,   1281281281,   128128,  //
      0,   1,   1281281282,   128128,  //
      1281280,   1281281,   128128,  //
      0,   1281,   1281282,   128128,  //
      1280,   1,   1281282,   128128,  //
      0,   1,   2,   1281283,   128128,  //
      1281281280,   1281,   128128,  //
      0,   1281281,   1282,   128128,  //
      1280,   1281,   1282,   128128,  //
      0,   1,   1282,   1283,   128128,  //
      1281280,   1,   1282,   128128,  //
      0,   1281,   2,   1283,   128128,  //
      1280,   1,   2,   1283,   128128,  //
      0,   1,   2,   3,   1284,   128128,  //
      1281281281280,   1,   128128,  //
      0,   1281281281,   2,   128128,  //
      1280,   1281281,   2,   128128,  //
      0,   1,   1281282,   3,   128128,  //
      1281280,   1281,   2,   128128,  //
      0,   1281,   1282,   3,   128128,  //
      1280,   1,   1282,   3,   128128,  //
      0,   1,   2,   1283,   4,   128128,  //
      1281281280,   1,   2,   128128,  //
      0,   1281281,   2,   3,   128128,  //
      1280,   1281,   2,   3,   128128,  //
      0,   1,   1282,   3,   4,   128128,  //
      1281280,   1,   2,   3,   128128,  //
      0,   1281,   2,   3,   4,   128128,  //
      1280,   1,   2,   3,   4,   128128,  //
      0,   1,   2,   3,   4,   5,   128128,  //
      1281281281281281280,   128,  //
      0,   1281281281281281,   128,  //
      1280,   1281281281281,   128,  //
      0,   1,   1281281281282,   128,  //
      1281280,   1281281281,   128,  //
      0,   1281,   1281281282,   128,  //
      1280,   1,   1281281282,   128,  //
      0,   1,   2,   1281281283,   128,  //
      1281281280,   1281281,   128,  //
      0,   1281281,   1281282,   128,  //
      1280,   1281,   1281282,   128,  //
      0,   1,   1282,   1281283,   128,  //
      1281280,   1,   1281282,   128,  //
      0,   1281,   2,   1281283,   128,  //
      1280,   1,   2,   1281283,   128,  //
      0,   1,   2,   3,   1281284,   128,  //
      1281281281280,   1281,   128,  //
      0,   1281281281,   1282,   128,  //
      1280,   1281281,   1282,   128,  //
      0,   1,   1281282,   1283,   128,  //
      1281280,   1281,   1282,   128,  //
      0,   1281,   1282,   1283,   128,  //
      1280,   1,   1282,   1283,   128,  //
      0,   1,   2,   1283,   1284,   128,  //
      1281281280,   1,   1282,   128,  //
      0,   1281281,   2,   1283,   128,  //
      1280,   1281,   2,   1283,   128,  //
      0,   1,   1282,   3,   1284,   128,  //
      1281280,   1,   2,   1283,   128,  //
      0,   1281,   2,   3,   1284,   128,  //
      1280,   1,   2,   3,   1284,   128,  //
      0,   1,   2,   3,   4,   1285,   128,  //
      1281281281281280,   1,   128,  //
      0,   1281281281281,   2,   128,  //
      1280,   1281281281,   2,   128,  //
      0,   1,   1281281282,   3,   128,  //
      1281280,   1281281,   2,   128,  //
      0,   1281,   1281282,   3,   128,  //
      1280,   1,   1281282,   3,   128,  //
      0,   1,   2,   1281283,   4,   128,  //
      1281281280,   1281,   2,   128,  //
      0,   1281281,   1282,   3,   128,  //
      1280,   1281,   1282,   3,   128,  //
      0,   1,   1282,   1283,   4,   128,  //
      1281280,   1,   1282,   3,   128,  //
      0,   1281,   2,   1283,   4,   128,  //
      1280,   1,   2,   1283,   4,   128,  //
      0,   1,   2,   3,   1284,   5,   128,  //
      1281281281280,   1,   2,   128,  //
      0,   1281281281,   2,   3,   128,  //
      1280,   1281281,   2,   3,   128,  //
      0,   1,   1281282,   3,   4,   128,  //
      1281280,   1281,   2,   3,   128,  //
      0,   1281,   1282,   3,   4,   128,  //
      1280,   1,   1282,   3,   4,   128,  //
      0,   1,   2,   1283,   4,   5,   128,  //
      1281281280,   1,   2,   3,   128,  //
      0,   1281281,   2,   3,   4,   128,  //
      1280,   1281,   2,   3,   4,   128,  //
      0,   1,   1282,   3,   4,   5,   128,  //
      1281280,   1,   2,   3,   4,   128,  //
      0,   1281,   2,   3,   4,   5,   128,  //
      1280,   1,   2,   3,   4,   5,   128,  //
      0,   1,   2,   3,   4,   5,   6,   128,  //
      1281281281281281281280,    //
      0,   1281281281281281281,    //
      1280,   1281281281281281,    //
      0,   1,   1281281281281282,    //
      1281280,   1281281281281,    //
      0,   1281,   1281281281282,    //
      1280,   1,   1281281281282,    //
      0,   1,   2,   1281281281283,    //
      1281281280,   1281281281,    //
      0,   1281281,   1281281282,    //
      1280,   1281,   1281281282,    //
      0,   1,   1282,   1281281283,    //
      1281280,   1,   1281281282,    //
      0,   1281,   2,   1281281283,    //
      1280,   1,   2,   1281281283,    //
      0,   1,   2,   3,   1281281284,    //
      1281281281280,   1281281,    //
      0,   1281281281,   1281282,    //
      1280,   1281281,   1281282,    //
      0,   1,   1281282,   1281283,    //
      1281280,   1281,   1281282,    //
      0,   1281,   1282,   1281283,    //
      1280,   1,   1282,   1281283,    //
      0,   1,   2,   1283,   1281284,    //
      1281281280,   1,   1281282,    //
      0,   1281281,   2,   1281283,    //
      1280,   1281,   2,   1281283,    //
      0,   1,   1282,   3,   1281284,    //
      1281280,   1,   2,   1281283,    //
      0,   1281,   2,   3,   1281284,    //
      1280,   1,   2,   3,   1281284,    //
      0,   1,   2,   3,   4,   1281285,    //
      1281281281281280,   1281,    //
      0,   1281281281281,   1282,    //
      1280,   1281281281,   1282,    //
      0,   1,   1281281282,   1283,    //
      1281280,   1281281,   1282,    //
      0,   1281,   1281282,   1283,    //
      1280,   1,   1281282,   1283,    //
      0,   1,   2,   1281283,   1284,    //
      1281281280,   1281,   1282,    //
      0,   1281281,   1282,   1283,    //
      1280,   1281,   1282,   1283,    //
      0,   1,   1282,   1283,   1284,    //
      1281280,   1,   1282,   1283,    //
      0,   1281,   2,   1283,   1284,    //
      1280,   1,   2,   1283,   1284,    //
      0,   1,   2,   3,   1284,   1285,    //
      1281281281280,   1,   1282,    //
      0,   1281281281,   2,   1283,    //
      1280,   1281281,   2,   1283,    //
      0,   1,   1281282,   3,   1284,    //
      1281280,   1281,   2,   1283,    //
      0,   1281,   1282,   3,   1284,    //
      1280,   1,   1282,   3,   1284,    //
      0,   1,   2,   1283,   4,   1285,    //
      1281281280,   1,   2,   1283,    //
      0,   1281281,   2,   3,   1284,    //
      1280,   1281,   2,   3,   1284,    //
      0,   1,   1282,   3,   4,   1285,    //
      1281280,   1,   2,   3,   1284,    //
      0,   1281,   2,   3,   4,   1285,    //
      1280,   1,   2,   3,   4,   1285,    //
      0,   1,   2,   3,   4,   5,   1286,    //
      1281281281281281280,   1,    //
      0,   1281281281281281,   2,    //
      1280,   1281281281281,   2,    //
      0,   1,   1281281281282,   3,    //
      1281280,   1281281281,   2,    //
      0,   1281,   1281281282,   3,    //
      1280,   1,   1281281282,   3,    //
      0,   1,   2,   1281281283,   4,    //
      1281281280,   1281281,   2,    //
      0,   1281281,   1281282,   3,    //
      1280,   1281,   1281282,   3,    //
      0,   1,   1282,   1281283,   4,    //
      1281280,   1,   1281282,   3,    //
      0,   1281,   2,   1281283,   4,    //
      1280,   1,   2,   1281283,   4,    //
      0,   1,   2,   3,   1281284,   5,    //
      1281281281280,   1281,   2,    //
      0,   1281281281,   1282,   3,    //
      1280,   1281281,   1282,   3,    //
      0,   1,   1281282,   1283,   4,    //
      1281280,   1281,   1282,   3,    //
      0,   1281,   1282,   1283,   4,    //
      1280,   1,   1282,   1283,   4,    //
      0,   1,   2,   1283,   1284,   5,    //
      1281281280,   1,   1282,   3,    //
      0,   1281281,   2,   1283,   4,    //
      1280,   1281,   2,   1283,   4,    //
      0,   1,   1282,   3,   1284,   5,    //
      1281280,   1,   2,   1283,   4,    //
      0,   1281,   2,   3,   1284,   5,    //
      1280,   1,   2,   3,   1284,   5,    //
      0,   1,   2,   3,   4,   1285,   6,    //
      1281281281281280,   1,   2,    //
      0,   1281281281281,   2,   3,    //
      1280,   1281281281,   2,   3,    //
      0,   1,   1281281282,   3,   4,    //
      1281280,   1281281,   2,   3,    //
      0,   1281,   1281282,   3,   4,    //
      1280,   1,   1281282,   3,   4,    //
      0,   1,   2,   1281283,   4,   5,    //
      1281281280,   1281,   2,   3,    //
      0,   1281281,   1282,   3,   4,    //
      1280,   1281,   1282,   3,   4,    //
      0,   1,   1282,   1283,   4,   5,    //
      1281280,   1,   1282,   3,   4,    //
      0,   1281,   2,   1283,   4,   5,    //
      1280,   1,   2,   1283,   4,   5,    //
      0,   1,   2,   3,   1284,   5,   6,    //
      1281281281280,   1,   2,   3,    //
      0,   1281281281,   2,   3,   4,    //
      1280,   1281281,   2,   3,   4,    //
      0,   1,   1281282,   3,   4,   5,    //
      1281280,   1281,   2,   3,   4,    //
      0,   1281,   1282,   3,   4,   5,    //
      1280,   1,   1282,   3,   4,   5,    //
      0,   1,   2,   1283,   4,   5,   6,    //
      1281281280,   1,   2,   3,   4,    //
      0,   1281281,   2,   3,   4,   5,    //
      1280,   1281,   2,   3,   4,   5,    //
      0,   1,   1282,   3,   4,   5,   6,    //
      1281280,   1,   2,   3,   4,   5,    //
      0,   1281,   2,   3,   4,   5,   6,    //
      1280,   1,   2,   3,   4,   5,   6,    //
      0,   1,   2,   3,   4,   5,   6,   7};
  return LoadU(du8, table + mask_bits * 8);
}

}  // namespace detail

// Half vector of bytes: one table lookup
template <typename T, size_t N, HWY_IF_T_SIZE(T, 1), HWY_IF_V_SIZE_LE(T, N, 8)>
HWY_API Vec128<T, N> Expand(Vec128<T, N> v, Mask128<T, N> mask) {
  const DFromV<decltype(v)> d;

  const uint64_t mask_bits = detail::BitsFromMask(mask);
  const Vec128<uint8_t, N> indices =
      detail::IndicesForExpandFromBits<N>(mask_bits);
  return BitCast(d, TableLookupBytesOr0(v, indices));
}

// Full vector of bytes: two table lookups
template <typename T, HWY_IF_T_SIZE(T, 1)>
HWY_API Vec128<T> Expand(Vec128<T> v, Mask128<T> mask) {
  const Full128<T> d;
  const RebindToUnsigned<decltype(d)> du;
  const Half<decltype(du)> duh;
  const Vec128<uint8_t> vu = BitCast(du, v);

  const uint64_t mask_bits = detail::BitsFromMask(mask);
  const uint64_t maskL = mask_bits & 0xFF;
  const uint64_t maskH = mask_bits >> 8;

  // We want to skip past the v bytes already consumed by idxL. There is no
  // instruction for shift-reg by variable bytes. Storing v itself would work
  // but would involve a store-load forwarding stall. We instead shuffle using
  // loaded indices. multishift_epi64_epi8 would also help, but if we have that,
  // we probably also have native 8-bit Expand.
  alignas(16static constexpr uint8_t iota[32] = {
      0,   1,   2,   3,   4,   5,   6,   7,   8,   9,   10,
      11,  12,  13,  14,  15,  128128128128128128,
      128128128128128128128128128128};
  const VFromD<decltype(du)> shift = LoadU(du, iota + PopCount(maskL));
  const VFromD<decltype(duh)> vL = LowerHalf(duh, vu);
  const VFromD<decltype(duh)> vH =
      LowerHalf(duh, TableLookupBytesOr0(vu, shift));

  const VFromD<decltype(duh)> idxL = detail::IndicesForExpandFromBits<8>(maskL);
  const VFromD<decltype(duh)> idxH = detail::IndicesForExpandFromBits<8>(maskH);

  const VFromD<decltype(duh)> expandL = TableLookupBytesOr0(vL, idxL);
  const VFromD<decltype(duh)> expandH = TableLookupBytesOr0(vH, idxH);
  return BitCast(d, Combine(du, expandH, expandL));
}

template <typename T, size_t N, HWY_IF_T_SIZE(T, 2)>
HWY_API Vec128<T, N> Expand(Vec128<T, N> v, Mask128<T, N> mask) {
  const DFromV<decltype(v)> d;
  const RebindToUnsigned<decltype(d)> du;

  const Rebind<uint8_t, decltype(d)> du8;
  const uint64_t mask_bits = detail::BitsFromMask(mask);

  // Storing as 8-bit reduces table size from 4 KiB to 2 KiB. We cannot apply
  // the nibble trick used below because not all indices fit within one lane.
  alignas(16static constexpr uint8_t table[2048] = {
      // PrintExpand16x8ByteTables
      128128128128128128128128,  //
      0,   128128128128128128128,  //
      1280,   128128128128128128,  //
      0,   2,   128128128128128128,  //
      1281280,   128128128128128,  //
      0,   1282,   128128128128128,  //
      1280,   2,   128128128128128,  //
      0,   2,   4,   128128128128128,  //
      1281281280,   128128128128,  //
      0,   1281282,   128128128128,  //
      1280,   1282,   128128128128,  //
      0,   2,   1284,   128128128128,  //
      1281280,   2,   128128128128,  //
      0,   1282,   4,   128128128128,  //
      1280,   2,   4,   128128128128,  //
      0,   2,   4,   6,   128128128128,  //
      1281281281280,   128128128,  //
      0,   1281281282,   128128128,  //
      1280,   1281282,   128128128,  //
      0,   2,   1281284,   128128128,  //
      1281280,   1282,   128128128,  //
      0,   1282,   1284,   128128128,  //
      1280,   2,   1284,   128128128,  //
      0,   2,   4,   1286,   128128128,  //
      1281281280,   2,   128128128,  //
      0,   1281282,   4,   128128128,  //
      1280,   1282,   4,   128128128,  //
      0,   2,   1284,   6,   128128128,  //
      1281280,   2,   4,   128128128,  //
      0,   1282,   4,   6,   128128128,  //
      1280,   2,   4,   6,   128128128,  //
      0,   2,   4,   6,   8,   128128128,  //
      1281281281281280,   128128,  //
      0,   1281281281282,   128128,  //
      1280,   1281281282,   128128,  //
      0,   2,   1281281284,   128128,  //
      1281280,   1281282,   128128,  //
      0,   1282,   1281284,   128128,  //
      1280,   2,   1281284,   128128,  //
      0,   2,   4,   1281286,   128128,  //
      1281281280,   1282,   128128,  //
      0,   1281282,   1284,   128128,  //
      1280,   1282,   1284,   128128,  //
      0,   2,   1284,   1286,   128128,  //
      1281280,   2,   1284,   128128,  //
      0,   1282,   4,   1286,   128128,  //
      1280,   2,   4,   1286,   128128,  //
      0,   2,   4,   6,   1288,   128128,  //
      1281281281280,   2,   128128,  //
      0,   1281281282,   4,   128128,  //
      1280,   1281282,   4,   128128,  //
      0,   2,   1281284,   6,   128128,  //
      1281280,   1282,   4,   128128,  //
      0,   1282,   1284,   6,   128128,  //
      1280,   2,   1284,   6,   128128,  //
      0,   2,   4,   1286,   8,   128128,  //
      1281281280,   2,   4,   128128,  //
      0,   1281282,   4,   6,   128128,  //
      1280,   1282,   4,   6,   128128,  //
      0,   2,   1284,   6,   8,   128128,  //
      1281280,   2,   4,   6,   128128,  //
      0,   1282,   4,   6,   8,   128128,  //
      1280,   2,   4,   6,   8,   128128,  //
      0,   2,   4,   6,   8,   10,  128128,  //
      1281281281281281280,   128,  //
      0,   1281281281281282,   128,  //
      1280,   1281281281282,   128,  //
      0,   2,   1281281281284,   128,  //
      1281280,   1281281282,   128,  //
      0,   1282,   1281281284,   128,  //
      1280,   2,   1281281284,   128,  //
      0,   2,   4,   1281281286,   128,  //
      1281281280,   1281282,   128,  //
      0,   1281282,   1281284,   128,  //
      1280,   1282,   1281284,   128,  //
      0,   2,   1284,   1281286,   128,  //
      1281280,   2,   1281284,   128,  //
      0,   1282,   4,   1281286,   128,  //
      1280,   2,   4,   1281286,   128,  //
      0,   2,   4,   6,   1281288,   128,  //
      1281281281280,   1282,   128,  //
      0,   1281281282,   1284,   128,  //
      1280,   1281282,   1284,   128,  //
      0,   2,   1281284,   1286,   128,  //
      1281280,   1282,   1284,   128,  //
      0,   1282,   1284,   1286,   128,  //
      1280,   2,   1284,   1286,   128,  //
      0,   2,   4,   1286,   1288,   128,  //
      1281281280,   2,   1284,   128,  //
      0,   1281282,   4,   1286,   128,  //
      1280,   1282,   4,   1286,   128,  //
      0,   2,   1284,   6,   1288,   128,  //
      1281280,   2,   4,   1286,   128,  //
      0,   1282,   4,   6,   1288,   128,  //
      1280,   2,   4,   6,   1288,   128,  //
      0,   2,   4,   6,   8,   12810,  128,  //
      1281281281281280,   2,   128,  //
      0,   1281281281282,   4,   128,  //
      1280,   1281281282,   4,   128,  //
      0,   2,   1281281284,   6,   128,  //
      1281280,   1281282,   4,   128,  //
      0,   1282,   1281284,   6,   128,  //
      1280,   2,   1281284,   6,   128,  //
      0,   2,   4,   1281286,   8,   128,  //
      1281281280,   1282,   4,   128,  //
      0,   1281282,   1284,   6,   128,  //
      1280,   1282,   1284,   6,   128,  //
      0,   2,   1284,   1286,   8,   128,  //
      1281280,   2,   1284,   6,   128,  //
      0,   1282,   4,   1286,   8,   128,  //
      1280,   2,   4,   1286,   8,   128,  //
      0,   2,   4,   6,   1288,   10,  128,  //
      1281281281280,   2,   4,   128,  //
      0,   1281281282,   4,   6,   128,  //
      1280,   1281282,   4,   6,   128,  //
      0,   2,   1281284,   6,   8,   128,  //
      1281280,   1282,   4,   6,   128,  //
      0,   1282,   1284,   6,   8,   128,  //
      1280,   2,   1284,   6,   8,   128,  //
      0,   2,   4,   1286,   8,   10,  128,  //
      1281281280,   2,   4,   6,   128,  //
      0,   1281282,   4,   6,   8,   128,  //
      1280,   1282,   4,   6,   8,   128,  //
      0,   2,   1284,   6,   8,   10,  128,  //
      1281280,   2,   4,   6,   8,   128,  //
      0,   1282,   4,   6,   8,   10,  128,  //
      1280,   2,   4,   6,   8,   10,  128,  //
      0,   2,   4,   6,   8,   10,  12,  128,  //
      1281281281281281281280,    //
      0,   1281281281281281282,    //
      1280,   1281281281281282,    //
      0,   2,   1281281281281284,    //
      1281280,   1281281281282,    //
      0,   1282,   1281281281284,    //
      1280,   2,   1281281281284,    //
      0,   2,   4,   1281281281286,    //
      1281281280,   1281281282,    //
      0,   1281282,   1281281284,    //
      1280,   1282,   1281281284,    //
      0,   2,   1284,   1281281286,    //
      1281280,   2,   1281281284,    //
      0,   1282,   4,   1281281286,    //
      1280,   2,   4,   1281281286,    //
      0,   2,   4,   6,   1281281288,    //
      1281281281280,   1281282,    //
      0,   1281281282,   1281284,    //
      1280,   1281282,   1281284,    //
      0,   2,   1281284,   1281286,    //
      1281280,   1282,   1281284,    //
      0,   1282,   1284,   1281286,    //
      1280,   2,   1284,   1281286,    //
      0,   2,   4,   1286,   1281288,    //
      1281281280,   2,   1281284,    //
      0,   1281282,   4,   1281286,    //
      1280,   1282,   4,   1281286,    //
      0,   2,   1284,   6,   1281288,    //
      1281280,   2,   4,   1281286,    //
      0,   1282,   4,   6,   1281288,    //
      1280,   2,   4,   6,   1281288,    //
      0,   2,   4,   6,   8,   12812810,   //
      1281281281281280,   1282,    //
      0,   1281281281282,   1284,    //
      1280,   1281281282,   1284,    //
      0,   2,   1281281284,   1286,    //
      1281280,   1281282,   1284,    //
      0,   1282,   1281284,   1286,    //
      1280,   2,   1281284,   1286,    //
      0,   2,   4,   1281286,   1288,    //
      1281281280,   1282,   1284,    //
      0,   1281282,   1284,   1286,    //
      1280,   1282,   1284,   1286,    //
      0,   2,   1284,   1286,   1288,    //
      1281280,   2,   1284,   1286,    //
      0,   1282,   4,   1286,   1288,    //
      1280,   2,   4,   1286,   1288,    //
      0,   2,   4,   6,   1288,   12810,   //
      1281281281280,   2,   1284,    //
      0,   1281281282,   4,   1286,    //
      1280,   1281282,   4,   1286,    //
      0,   2,   1281284,   6,   1288,    //
      1281280,   1282,   4,   1286,    //
      0,   1282,   1284,   6,   1288,    //
      1280,   2,   1284,   6,   1288,    //
      0,   2,   4,   1286,   8,   12810,   //
      1281281280,   2,   4,   1286,    //
      0,   1281282,   4,   6,   1288,    //
      1280,   1282,   4,   6,   1288,    //
      0,   2,   1284,   6,   8,   12810,   //
      1281280,   2,   4,   6,   1288,    //
      0,   1282,   4,   6,   8,   12810,   //
      1280,   2,   4,   6,   8,   12810,   //
      0,   2,   4,   6,   8,   10,  12812,   //
      1281281281281281280,   2,    //
      0,   1281281281281282,   4,    //
      1280,   1281281281282,   4,    //
      0,   2,   1281281281284,   6,    //
      1281280,   1281281282,   4,    //
      0,   1282,   1281281284,   6,    //
      1280,   2,   1281281284,   6,    //
      0,   2,   4,   1281281286,   8,    //
      1281281280,   1281282,   4,    //
      0,   1281282,   1281284,   6,    //
      1280,   1282,   1281284,   6,    //
      0,   2,   1284,   1281286,   8,    //
      1281280,   2,   1281284,   6,    //
      0,   1282,   4,   1281286,   8,    //
      1280,   2,   4,   1281286,   8,    //
      0,   2,   4,   6,   1281288,   10,   //
      1281281281280,   1282,   4,    //
      0,   1281281282,   1284,   6,    //
      1280,   1281282,   1284,   6,    //
      0,   2,   1281284,   1286,   8,    //
      1281280,   1282,   1284,   6,    //
      0,   1282,   1284,   1286,   8,    //
      1280,   2,   1284,   1286,   8,    //
      0,   2,   4,   1286,   1288,   10,   //
      1281281280,   2,   1284,   6,    //
      0,   1281282,   4,   1286,   8,    //
      1280,   1282,   4,   1286,   8,    //
      0,   2,   1284,   6,   1288,   10,   //
      1281280,   2,   4,   1286,   8,    //
      0,   1282,   4,   6,   1288,   10,   //
      1280,   2,   4,   6,   1288,   10,   //
      0,   2,   4,   6,   8,   12810,  12,   //
      1281281281281280,   2,   4,    //
      0,   1281281281282,   4,   6,    //
      1280,   1281281282,   4,   6,    //
      0,   2,   1281281284,   6,   8,    //
      1281280,   1281282,   4,   6,    //
      0,   1282,   1281284,   6,   8,    //
      1280,   2,   1281284,   6,   8,    //
      0,   2,   4,   1281286,   8,   10,   //
      1281281280,   1282,   4,   6,    //
      0,   1281282,   1284,   6,   8,    //
      1280,   1282,   1284,   6,   8,    //
      0,   2,   1284,   1286,   8,   10,   //
      1281280,   2,   1284,   6,   8,    //
      0,   1282,   4,   1286,   8,   10,   //
      1280,   2,   4,   1286,   8,   10,   //
      0,   2,   4,   6,   1288,   10,  12,   //
      1281281281280,   2,   4,   6,    //
      0,   1281281282,   4,   6,   8,    //
      1280,   1281282,   4,   6,   8,    //
      0,   2,   1281284,   6,   8,   10,   //
      1281280,   1282,   4,   6,   8,    //
      0,   1282,   1284,   6,   8,   10,   //
      1280,   2,   1284,   6,   8,   10,   //
      0,   2,   4,   1286,   8,   10,  12,   //
      1281281280,   2,   4,   6,   8,    //
      0,   1281282,   4,   6,   8,   10,   //
      1280,   1282,   4,   6,   8,   10,   //
      0,   2,   1284,   6,   8,   10,  12,   //
      1281280,   2,   4,   6,   8,   10,   //
      0,   1282,   4,   6,   8,   10,  12,   //
      1280,   2,   4,   6,   8,   10,  12,   //
      0,   2,   4,   6,   8,   10,  12,  14};
  // Extend to double length because InterleaveLower will only use the (valid)
  // lower half, and we want N u16.
  const Twice<decltype(du8)> du8x2;
  const Vec128<uint8_t, 2 * N> indices8 =
      ZeroExtendVector(du8x2, Load(du8, table + mask_bits * 8));
  const Vec128<uint16_t, N> indices16 =
      BitCast(du, InterleaveLower(du8x2, indices8, indices8));
  // TableLookupBytesOr0 operates on bytes. To convert u16 lane indices to byte
  // indices, add 0 to even and 1 to odd byte lanes.
  const Vec128<uint16_t, N> byte_indices = Add(
      indices16,
      Set(du, static_cast<uint16_t>(HWY_IS_LITTLE_ENDIAN ? 0x0100 : 0x0001)));
  return BitCast(d, TableLookupBytesOr0(v, byte_indices));
}

template <typename T, size_t N, HWY_IF_T_SIZE(T, 4)>
HWY_API Vec128<T, N> Expand(Vec128<T, N> v, Mask128<T, N> mask) {
  const DFromV<decltype(v)> d;
  const RebindToUnsigned<decltype(d)> du;

  const uint64_t mask_bits = detail::BitsFromMask(mask);

  alignas(16static constexpr uint32_t packed_array[16] = {
      // PrintExpand64x4Nibble - same for 32x4.
      0x0000ffff, 0x0000fff0, 0x0000ff0f, 0x0000ff10, 0x0000f0ff, 0x0000f1f0,
      0x0000f10f, 0x0000f210, 0x00000fff, 0x00001ff0, 0x00001f0f, 0x00002f10,
      0x000010ff, 0x000021f0, 0x0000210f, 0x00003210};

  // For lane i, shift the i-th 4-bit index down to bits [0, 2).
  const Vec128<uint32_t, N> packed = Set(du, packed_array[mask_bits]);
  alignas(16static constexpr uint32_t shifts[4] = {04812};
  Vec128<uint32_t, N> indices = packed >> Load(du, shifts);
  // AVX2 _mm256_permutexvar_epi32 will ignore upper bits, but IndicesFromVec
  // checks bounds, so clear the upper bits.
  indices = And(indices, Set(du, N - 1));
  const Vec128<uint32_t, N> expand =
      TableLookupLanes(BitCast(du, v), IndicesFromVec(du, indices));
  // TableLookupLanes cannot also zero masked-off lanes, so do that now.
  return IfThenElseZero(mask, BitCast(d, expand));
}

template <typename T, HWY_IF_T_SIZE(T, 8)>
HWY_API Vec128<T> Expand(Vec128<T> v, Mask128<T> mask) {
  // Same as Compress, just zero out the mask=false lanes.
  return IfThenElseZero(mask, Compress(v, mask));
}

// For single-element vectors, this is at least as fast as native.
template <typename T>
HWY_API Vec128<T, 1> Expand(Vec128<T, 1> v, Mask128<T, 1> mask) {
  return IfThenElseZero(mask, v);
}

// ------------------------------ LoadExpand
template <class D, HWY_IF_V_SIZE_LE_D(D, 16)>
HWY_API VFromD<D> LoadExpand(MFromD<D> mask, D d,
                             const TFromD<D>* HWY_RESTRICT unaligned) {
  return Expand(LoadU(d, unaligned), mask);
}

#endif  // HWY_NATIVE_EXPAND

// ------------------------------ TwoTablesLookupLanes

template <class D>
using IndicesFromD = decltype(IndicesFromVec(D(), Zero(RebindToUnsigned<D>())));

// RVV/SVE have their own implementations of
// TwoTablesLookupLanes(D d, VFromD<D> a, VFromD<D> b, IndicesFromD<D> idx)
#if HWY_TARGET != HWY_RVV && HWY_TARGET != HWY_SVE &&      \
    HWY_TARGET != HWY_SVE2 && HWY_TARGET != HWY_SVE_256 && \
    HWY_TARGET != HWY_SVE2_128
template <class D>
HWY_API VFromD<D> TwoTablesLookupLanes(D /*d*/, VFromD<D> a, VFromD<D> b,
                                       IndicesFromD<D> idx) {
  return TwoTablesLookupLanes(a, b, idx);
}
#endif

// ------------------------------ Reverse2, Reverse4, Reverse8 (8-bit)

#if (defined(HWY_NATIVE_REVERSE2_8) == defined(HWY_TARGET_TOGGLE)) || HWY_IDE
#ifdef HWY_NATIVE_REVERSE2_8
#undef HWY_NATIVE_REVERSE2_8
#else
#define HWY_NATIVE_REVERSE2_8
#endif

#undef HWY_PREFER_ROTATE
// Platforms on which RotateRight is likely faster than TableLookupBytes.
// RVV and SVE anyway have their own implementation of this.
#if HWY_TARGET == HWY_SSE2 || HWY_TARGET <= HWY_AVX3 || \
    HWY_TARGET == HWY_WASM || HWY_TARGET == HWY_PPC8
#define HWY_PREFER_ROTATE 1
#else
#define HWY_PREFER_ROTATE 0
#endif

template <class D, HWY_IF_T_SIZE_D(D, 1)>
HWY_API VFromD<D> Reverse2(D d, VFromD<D> v) {
  // Exclude AVX3 because its 16-bit RotateRight is actually 3 instructions.
#if HWY_PREFER_ROTATE && HWY_TARGET > HWY_AVX3
  const Repartition<uint16_t, decltype(d)> du16;
  return BitCast(d, RotateRight<8>(BitCast(du16, v)));
#else
  const VFromD<D> shuffle = Dup128VecFromValues(d, 1032547698,
                                                111013121514);
  return TableLookupBytes(v, shuffle);
#endif
}

template <class D, HWY_IF_T_SIZE_D(D, 1)>
HWY_API VFromD<D> Reverse4(D d, VFromD<D> v) {
#if HWY_PREFER_ROTATE
  const Repartition<uint16_t, decltype(d)> du16;
  return BitCast(d, Reverse2(du16, BitCast(du16, Reverse2(d, v))));
#else
  const Repartition<uint8_t, decltype(d)> du8;
  const VFromD<decltype(du8)> shuffle = Dup128VecFromValues(
      du8, 3210765411109815141312);
  return TableLookupBytes(v, BitCast(d, shuffle));
#endif
}

template <class D, HWY_IF_T_SIZE_D(D, 1)>
HWY_API VFromD<D> Reverse8(D d, VFromD<D> v) {
#if HWY_PREFER_ROTATE
  const Repartition<uint32_t, D> du32;
  return BitCast(d, Reverse2(du32, BitCast(du32, Reverse4(d, v))));
#else
  const Repartition<uint8_t, decltype(d)> du8;
  const VFromD<decltype(du8)> shuffle = Dup128VecFromValues(
      du8, 7654321015141312111098);
  return TableLookupBytes(v, BitCast(d, shuffle));
#endif
}

#endif  // HWY_NATIVE_REVERSE2_8

// ------------------------------ ReverseLaneBytes

#if (defined(HWY_NATIVE_REVERSE_LANE_BYTES) == defined(HWY_TARGET_TOGGLE))
#ifdef HWY_NATIVE_REVERSE_LANE_BYTES
#undef HWY_NATIVE_REVERSE_LANE_BYTES
#else
#define HWY_NATIVE_REVERSE_LANE_BYTES
#endif

template <class V, HWY_IF_T_SIZE_V(V, 2)>
HWY_API V ReverseLaneBytes(V v) {
  const DFromV<V> d;
  const Repartition<uint8_t, decltype(d)> du8;
  return BitCast(d, Reverse2(du8, BitCast(du8, v)));
}

template <class V, HWY_IF_T_SIZE_V(V, 4)>
HWY_API V ReverseLaneBytes(V v) {
  const DFromV<V> d;
  const Repartition<uint8_t, decltype(d)> du8;
  return BitCast(d, Reverse4(du8, BitCast(du8, v)));
}

template <class V, HWY_IF_T_SIZE_V(V, 8)>
HWY_API V ReverseLaneBytes(V v) {
  const DFromV<V> d;
  const Repartition<uint8_t, decltype(d)> du8;
  return BitCast(d, Reverse8(du8, BitCast(du8, v)));
}

#endif  // HWY_NATIVE_REVERSE_LANE_BYTES

// ------------------------------ ReverseBits

// On these targets, we emulate 8-bit shifts using 16-bit shifts and therefore
// require at least two lanes to BitCast to 16-bit. We avoid Highway's 8-bit
// shifts because those would add extra masking already taken care of by
// UI8ReverseBitsStep. Note that AVX3_DL/AVX3_ZEN4 support GFNI and use it to
// implement ReverseBits, so this code is not used there.
#undef HWY_REVERSE_BITS_MIN_BYTES
#if ((HWY_TARGET >= HWY_AVX3 && HWY_TARGET <= HWY_SSE2) || \
     HWY_TARGET == HWY_WASM || HWY_TARGET == HWY_WASM_EMU256)
#define HWY_REVERSE_BITS_MIN_BYTES 2
#else
#define HWY_REVERSE_BITS_MIN_BYTES 1
#endif

#if (defined(HWY_NATIVE_REVERSE_BITS_UI8) == defined(HWY_TARGET_TOGGLE))
#ifdef HWY_NATIVE_REVERSE_BITS_UI8
#undef HWY_NATIVE_REVERSE_BITS_UI8
#else
#define HWY_NATIVE_REVERSE_BITS_UI8
#endif

namespace detail {

template <int kShiftAmt, int kShrResultMask, class V,
          HWY_IF_V_SIZE_GT_D(DFromV<V>, HWY_REVERSE_BITS_MIN_BYTES - 1)>
HWY_INLINE V UI8ReverseBitsStep(V v) {
  const DFromV<decltype(v)> d;
  const RebindToUnsigned<decltype(d)> du;
#if HWY_REVERSE_BITS_MIN_BYTES == 2
  const Repartition<uint16_t, decltype(d)> d_shift;
#else
  const RebindToUnsigned<decltype(d)> d_shift;
#endif

  const auto v_to_shift = BitCast(d_shift, v);
  const auto shl_result = BitCast(d, ShiftLeft<kShiftAmt>(v_to_shift));
  const auto shr_result = BitCast(d, ShiftRight<kShiftAmt>(v_to_shift));
  const auto shr_result_mask =
      BitCast(d, Set(du, static_cast<uint8_t>(kShrResultMask)));
  return Or(And(shr_result, shr_result_mask),
            AndNot(shr_result_mask, shl_result));
}

#if HWY_REVERSE_BITS_MIN_BYTES == 2
template <int kShiftAmt, int kShrResultMask, class V,
          HWY_IF_V_SIZE_D(DFromV<V>, 1)>
HWY_INLINE V UI8ReverseBitsStep(V v) {
  return V{UI8ReverseBitsStep<kShiftAmt, kShrResultMask>(Vec128<uint8_t>{v.raw})
               .raw};
}
#endif

}  // namespace detail

template <class V, HWY_IF_T_SIZE_V(V, 1)>
HWY_API V ReverseBits(V v) {
  auto result = detail::UI8ReverseBitsStep<10x55>(v);
  result = detail::UI8ReverseBitsStep<20x33>(result);
  result = detail::UI8ReverseBitsStep<40x0F>(result);
  return result;
}

#endif  // HWY_NATIVE_REVERSE_BITS_UI8

#if (defined(HWY_NATIVE_REVERSE_BITS_UI16_32_64) == defined(HWY_TARGET_TOGGLE))
#ifdef HWY_NATIVE_REVERSE_BITS_UI16_32_64
#undef HWY_NATIVE_REVERSE_BITS_UI16_32_64
#else
#define HWY_NATIVE_REVERSE_BITS_UI16_32_64
#endif

template <class V, HWY_IF_T_SIZE_ONE_OF_V(V, (1 << 2) | (1 << 4) | (1 << 8)),
          HWY_IF_NOT_FLOAT_NOR_SPECIAL_V(V)>
HWY_API V ReverseBits(V v) {
  const DFromV<decltype(v)> d;
  const Repartition<uint8_t, decltype(d)> du8;
  return ReverseLaneBytes(BitCast(d, ReverseBits(BitCast(du8, v))));
}
#endif  // HWY_NATIVE_REVERSE_BITS_UI16_32_64

// ------------------------------ Per4LaneBlockShuffle

#if (defined(HWY_NATIVE_PER4LANEBLKSHUF_DUP32) == defined(HWY_TARGET_TOGGLE))
#ifdef HWY_NATIVE_PER4LANEBLKSHUF_DUP32
#undef HWY_NATIVE_PER4LANEBLKSHUF_DUP32
#else
#define HWY_NATIVE_PER4LANEBLKSHUF_DUP32
#endif

#if HWY_TARGET != HWY_SCALAR
namespace detail {

template <class D>
HWY_INLINE Vec<D> Per4LaneBlkShufDupSet4xU32(D d, const uint32_t x3,
                                             const uint32_t x2,
                                             const uint32_t x1,
                                             const uint32_t x0) {
#if HWY_TARGET == HWY_RVV
  constexpr int kPow2 = d.Pow2();
  constexpr int kLoadPow2 = HWY_MAX(kPow2, -1);
  const ScalableTag<uint32_t, kLoadPow2> d_load;
#else
  constexpr size_t kMaxBytes = d.MaxBytes();
#if HWY_TARGET == HWY_NEON || HWY_TARGET == HWY_NEON_WITHOUT_AES
  constexpr size_t kMinLanesToLoad = 2;
#else
  constexpr size_t kMinLanesToLoad = 4;
#endif
  constexpr size_t kNumToLoad =
      HWY_MAX(kMaxBytes / sizeof(uint32_t), kMinLanesToLoad);
  const CappedTag<uint32_t, kNumToLoad> d_load;
#endif
  return ResizeBitCast(d, Dup128VecFromValues(d_load, x0, x1, x2, x3));
}

}  // namespace detail
#endif

#endif  // HWY_NATIVE_PER4LANEBLKSHUF_DUP32

#if HWY_TARGET != HWY_SCALAR
namespace detail {

template <class V>
HWY_INLINE V Per2LaneBlockShuffle(hwy::SizeTag<0/*idx_10_tag*/, V v) {
  return DupEven(v);
}

template <class V>
HWY_INLINE V Per2LaneBlockShuffle(hwy::SizeTag<1/*idx_10_tag*/, V v) {
  const DFromV<decltype(v)> d;
  return Reverse2(d, v);
}

template <class V>
HWY_INLINE V Per2LaneBlockShuffle(hwy::SizeTag<2/*idx_10_tag*/, V v) {
  return v;
}

template <class V>
HWY_INLINE V Per2LaneBlockShuffle(hwy::SizeTag<3/*idx_10_tag*/, V v) {
  return DupOdd(v);
}

HWY_INLINE uint32_t U8x4Per4LaneBlkIndices(const uint32_t idx3,
                                           const uint32_t idx2,
                                           const uint32_t idx1,
                                           const uint32_t idx0) {
#if HWY_IS_LITTLE_ENDIAN
  return static_cast<uint32_t>((idx3 << 24) | (idx2 << 16) | (idx1 << 8) |
                               idx0);
#else
  return static_cast<uint32_t>(idx3 | (idx2 << 8) | (idx1 << 16) |
                               (idx0 << 24));
#endif
}

template <class D>
HWY_INLINE Vec<D> TblLookupPer4LaneBlkU8IdxInBlk(D d, const uint32_t idx3,
                                                 const uint32_t idx2,
                                                 const uint32_t idx1,
                                                 const uint32_t idx0) {
#if HWY_TARGET == HWY_RVV
  const AdjustSimdTagToMinVecPow2<Repartition<uint32_t, D>> du32;
#else
  const Repartition<uint32_t, D> du32;
#endif

  return ResizeBitCast(
      d, Set(du32, U8x4Per4LaneBlkIndices(idx3, idx2, idx1, idx0)));
}

#if HWY_HAVE_SCALABLE || HWY_TARGET == HWY_SVE_256 || \
    HWY_TARGET == HWY_SVE2_128 || HWY_TARGET == HWY_EMU128
#define HWY_PER_4_BLK_TBL_LOOKUP_LANES_ENABLE(D) void* = nullptr
#else
#define HWY_PER_4_BLK_TBL_LOOKUP_LANES_ENABLE(D) HWY_IF_T_SIZE_D(D, 8)

template <class V, HWY_IF_T_SIZE_ONE_OF_V(V, (1 << 1) | (1 << 2) | (1 << 4))>
HWY_INLINE V Per4LaneBlkShufDoTblLookup(V v, V idx) {
  const DFromV<decltype(v)> d;
  const Repartition<uint8_t, decltype(d)> du8;
  return BitCast(d, TableLookupBytes(BitCast(du8, v), BitCast(du8, idx)));
}

template <class D, HWY_IF_T_SIZE_D(D, 1)>
HWY_INLINE Vec<D> TblLookupPer4LaneBlkShufIdx(D d, const uint32_t idx3,
                                              const uint32_t idx2,
                                              const uint32_t idx1,
                                              const uint32_t idx0) {
  const Repartition<uint32_t, decltype(d)> du32;
  const uint32_t idx3210 = U8x4Per4LaneBlkIndices(idx3, idx2, idx1, idx0);
  const auto v_byte_idx = Per4LaneBlkShufDupSet4xU32(
      du32, static_cast<uint32_t>(idx3210 + 0x0C0C0C0C),
      static_cast<uint32_t>(idx3210 + 0x08080808),
      static_cast<uint32_t>(idx3210 + 0x04040404),
      static_cast<uint32_t>(idx3210));
  return ResizeBitCast(d, v_byte_idx);
}

template <class D, HWY_IF_T_SIZE_D(D, 2)>
HWY_INLINE Vec<D> TblLookupPer4LaneBlkShufIdx(D d, const uint32_t idx3,
                                              const uint32_t idx2,
                                              const uint32_t idx1,
                                              const uint32_t idx0) {
  const Repartition<uint32_t, decltype(d)> du32;
#if HWY_IS_LITTLE_ENDIAN
  const uint32_t idx10 = static_cast<uint32_t>((idx1 << 16) | idx0);
  const uint32_t idx32 = static_cast<uint32_t>((idx3 << 16) | idx2);
  constexpr uint32_t kLaneByteOffsets{0x01000100};
#else
  const uint32_t idx10 = static_cast<uint32_t>(idx1 | (idx0 << 16));
  const uint32_t idx32 = static_cast<uint32_t>(idx3 | (idx2 << 16));
  constexpr uint32_t kLaneByteOffsets{0x00010001};
#endif
  constexpr uint32_t kHiLaneByteOffsets{kLaneByteOffsets + 0x08080808u};

  const auto v_byte_idx = Per4LaneBlkShufDupSet4xU32(
      du32, static_cast<uint32_t>(idx32 * 0x0202u + kHiLaneByteOffsets),
      static_cast<uint32_t>(idx10 * 0x0202u + kHiLaneByteOffsets),
      static_cast<uint32_t>(idx32 * 0x0202u + kLaneByteOffsets),
      static_cast<uint32_t>(idx10 * 0x0202u + kLaneByteOffsets));
  return ResizeBitCast(d, v_byte_idx);
}

template <class D, HWY_IF_T_SIZE_D(D, 4)>
HWY_INLINE Vec<D> TblLookupPer4LaneBlkShufIdx(D d, const uint32_t idx3,
                                              const uint32_t idx2,
                                              const uint32_t idx1,
                                              const uint32_t idx0) {
  const Repartition<uint32_t, decltype(d)> du32;
#if HWY_IS_LITTLE_ENDIAN
  constexpr uint32_t kLaneByteOffsets{0x03020100};
#else
  constexpr uint32_t kLaneByteOffsets{0x00010203};
#endif

  const auto v_byte_idx = Per4LaneBlkShufDupSet4xU32(
      du32, static_cast<uint32_t>(idx3 * 0x04040404u + kLaneByteOffsets),
      static_cast<uint32_t>(idx2 * 0x04040404u + kLaneByteOffsets),
      static_cast<uint32_t>(idx1 * 0x04040404u + kLaneByteOffsets),
      static_cast<uint32_t>(idx0 * 0x04040404u + kLaneByteOffsets));
  return ResizeBitCast(d, v_byte_idx);
}
#endif

template <class D, HWY_IF_T_SIZE_D(D, 1)>
HWY_INLINE VFromD<D> TblLookupPer4LaneBlkIdxInBlk(D d, const uint32_t idx3,
                                                  const uint32_t idx2,
                                                  const uint32_t idx1,
                                                  const uint32_t idx0) {
  return TblLookupPer4LaneBlkU8IdxInBlk(d, idx3, idx2, idx1, idx0);
}

#if HWY_TARGET == HWY_RVV
template <class D, HWY_IF_NOT_T_SIZE_D(D, 1)>
HWY_INLINE VFromD<D> TblLookupPer4LaneBlkIdxInBlk(D d, const uint32_t idx3,
                                                  const uint32_t idx2,
                                                  const uint32_t idx1,
                                                  const uint32_t idx0) {
  const Rebind<uint8_t, decltype(d)> du8;
  return PromoteTo(d,
                   TblLookupPer4LaneBlkU8IdxInBlk(du8, idx3, idx2, idx1, idx0));
}
#else
template <class D, HWY_IF_T_SIZE_D(D, 2)>
HWY_INLINE VFromD<D> TblLookupPer4LaneBlkIdxInBlk(D d, const uint32_t idx3,
                                                  const uint32_t idx2,
                                                  const uint32_t idx1,
                                                  const uint32_t idx0) {
  const uint16_t u16_idx0 = static_cast<uint16_t>(idx0);
  const uint16_t u16_idx1 = static_cast<uint16_t>(idx1);
  const uint16_t u16_idx2 = static_cast<uint16_t>(idx2);
  const uint16_t u16_idx3 = static_cast<uint16_t>(idx3);
#if HWY_TARGET == HWY_NEON || HWY_TARGET == HWY_NEON_WITHOUT_AES
  constexpr size_t kMinLanesToLoad = 4;
#else
  constexpr size_t kMinLanesToLoad = 8;
#endif
  constexpr size_t kNumToLoad = HWY_MAX(HWY_MAX_LANES_D(D), kMinLanesToLoad);
  const CappedTag<uint16_t, kNumToLoad> d_load;
  return ResizeBitCast(
      d, Dup128VecFromValues(d_load, u16_idx0, u16_idx1, u16_idx2, u16_idx3,
                             u16_idx0, u16_idx1, u16_idx2, u16_idx3));
}

template <class D, HWY_IF_T_SIZE_D(D, 4)>
HWY_INLINE VFromD<D> TblLookupPer4LaneBlkIdxInBlk(D d, const uint32_t idx3,
                                                  const uint32_t idx2,
                                                  const uint32_t idx1,
                                                  const uint32_t idx0) {
  return Per4LaneBlkShufDupSet4xU32(d, idx3, idx2, idx1, idx0);
}

template <class D, HWY_IF_T_SIZE_D(D, 8)>
HWY_INLINE VFromD<D> TblLookupPer4LaneBlkIdxInBlk(D d, const uint32_t idx3,
                                                  const uint32_t idx2,
                                                  const uint32_t idx1,
                                                  const uint32_t idx0) {
  const RebindToUnsigned<decltype(d)> du;
  const Rebind<uint32_t, decltype(d)> du32;
  return BitCast(d, PromoteTo(du, Per4LaneBlkShufDupSet4xU32(du32, idx3, idx2,
                                                             idx1, idx0)));
}
#endif

template <class D, HWY_PER_4_BLK_TBL_LOOKUP_LANES_ENABLE(D)>
HWY_INLINE IndicesFromD<D> TblLookupPer4LaneBlkShufIdx(D d, const uint32_t idx3,
                                                       const uint32_t idx2,
                                                       const uint32_t idx1,
                                                       const uint32_t idx0) {
  const RebindToUnsigned<decltype(d)> du;
  using TU = TFromD<decltype(du)>;
  auto idx_in_blk = TblLookupPer4LaneBlkIdxInBlk(du, idx3, idx2, idx1, idx0);

  constexpr size_t kN = HWY_MAX_LANES_D(D);
  if (kN < 4) {
    idx_in_blk = And(idx_in_blk, Set(du, static_cast<TU>(kN - 1)));
  }

#if HWY_TARGET == HWY_RVV
  const auto blk_offsets = AndS(Iota0(du), static_cast<TU>(~TU{3}));
#else
  const auto blk_offsets =
      And(Iota(du, TU{0}), Set(du, static_cast<TU>(~TU{3})));
#endif
  return IndicesFromVec(d, Add(idx_in_blk, blk_offsets));
}

template <class V, HWY_PER_4_BLK_TBL_LOOKUP_LANES_ENABLE(DFromV<V>)>
HWY_INLINE V Per4LaneBlkShufDoTblLookup(V v, IndicesFromD<DFromV<V>> idx) {
  return TableLookupLanes(v, idx);
}

#undef HWY_PER_4_BLK_TBL_LOOKUP_LANES_ENABLE

template <class V>
HWY_INLINE V TblLookupPer4LaneBlkShuf(V v, size_t idx3210) {
  const DFromV<decltype(v)> d;
  const uint32_t idx3 = static_cast<uint32_t>((idx3210 >> 6) & 3);
  const uint32_t idx2 = static_cast<uint32_t>((idx3210 >> 4) & 3);
  const uint32_t idx1 = static_cast<uint32_t>((idx3210 >> 2) & 3);
  const uint32_t idx0 = static_cast<uint32_t>(idx3210 & 3);
  const auto idx = TblLookupPer4LaneBlkShufIdx(d, idx3, idx2, idx1, idx0);
  return Per4LaneBlkShufDoTblLookup(v, idx);
}

// The detail::Per4LaneBlockShuffle overloads that have the extra lane_size_tag
// and vect_size_tag parameters are only called for vectors that have at
// least 4 lanes (or scalable vectors that might possibly have 4 or more lanes)
template <size_t kIdx3210, size_t kLaneSize, size_t kVectSize, class V>
HWY_INLINE V Per4LaneBlockShuffle(hwy::SizeTag<kIdx3210> /*idx_3210_tag*/,
                                  hwy::SizeTag<kLaneSize> /*lane_size_tag*/,
                                  hwy::SizeTag<kVectSize> /*vect_size_tag*/,
                                  V v) {
  return TblLookupPer4LaneBlkShuf(v, kIdx3210);
}

#if HWY_HAVE_FLOAT64
template <class V>
HWY_INLINE VFromD<RepartitionToWide<DFromV<V>>> Per4LaneBlockShufCastToWide(
    hwy::FloatTag /* type_tag */, hwy::SizeTag<4> /* lane_size_tag */, V v) {
  const DFromV<decltype(v)> d;
  const RepartitionToWide<decltype(d)> dw;
  return BitCast(dw, v);
}
#endif

template <size_t kLaneSize, class V>
HWY_INLINE VFromD<RepartitionToWide<RebindToUnsigned<DFromV<V>>>>
Per4LaneBlockShufCastToWide(hwy::FloatTag /* type_tag */,
                            hwy::SizeTag<kLaneSize> /* lane_size_tag */, V v) {
  const DFromV<decltype(v)> d;
  const RebindToUnsigned<decltype(d)> du;
  const RepartitionToWide<decltype(du)> dw;
  return BitCast(dw, v);
}

template <size_t kLaneSize, class V>
HWY_INLINE VFromD<RepartitionToWide<DFromV<V>>> Per4LaneBlockShufCastToWide(
    hwy::NonFloatTag /* type_tag */,
    hwy::SizeTag<kLaneSize> /* lane_size_tag */, V v) {
  const DFromV<decltype(v)> d;
  const RepartitionToWide<decltype(d)> dw;
  return BitCast(dw, v);
}

template <class V>
HWY_INLINE V Per4LaneBlockShuffle(hwy::SizeTag<0x1B> /*idx_3210_tag*/, V v) {
  const DFromV<decltype(v)> d;
  return Reverse4(d, v);
}

template <class V,
          HWY_IF_T_SIZE_ONE_OF_V(V, (1 << 1) | (1 << 2) |
                                        (HWY_HAVE_INTEGER64 ? (1 << 4) : 0))>
HWY_INLINE V Per4LaneBlockShuffle(hwy::SizeTag<0x44> /*idx_3210_tag*/, V v) {
  const DFromV<decltype(v)> d;
  const auto vw = Per4LaneBlockShufCastToWide(
      hwy::IsFloatTag<TFromV<V>>(), hwy::SizeTag<sizeof(TFromV<V>)>(), v);
  return BitCast(d, DupEven(vw));
}

template <class V,
          HWY_IF_T_SIZE_ONE_OF_V(V, (1 << 1) | (1 << 2) |
                                        (HWY_HAVE_INTEGER64 ? (1 << 4) : 0))>
HWY_INLINE V Per4LaneBlockShuffle(hwy::SizeTag<0x4E> /*idx_3210_tag*/, V v) {
  const DFromV<decltype(v)> d;
  const auto vw = Per4LaneBlockShufCastToWide(
      hwy::IsFloatTag<TFromV<V>>(), hwy::SizeTag<sizeof(TFromV<V>)>(), v);
  const DFromV<decltype(vw)> dw;
  return BitCast(d, Reverse2(dw, vw));
}

#if HWY_MAX_BYTES >= 32
template <class V, HWY_IF_T_SIZE_V(V, 8)>
HWY_INLINE V Per4LaneBlockShuffle(hwy::SizeTag<0x4E> /*idx_3210_tag*/, V v) {
  return SwapAdjacentBlocks(v);
}
#endif

template <class V, HWY_IF_LANES_D(DFromV<V>, 4),
          HWY_IF_T_SIZE_ONE_OF_V(V, (1 << 1) | (1 << 2))>
HWY_INLINE V Per4LaneBlockShuffle(hwy::SizeTag<0x50> /*idx_3210_tag*/, V v) {
  const DFromV<decltype(v)> d;
  return InterleaveLower(d, v, v);
}

template <class V, HWY_IF_T_SIZE_V(V, 4)>
HWY_INLINE V Per4LaneBlockShuffle(hwy::SizeTag<0x50> /*idx_3210_tag*/, V v) {
  const DFromV<decltype(v)> d;
  return InterleaveLower(d, v, v);
}

template <class V, HWY_IF_LANES_D(DFromV<V>, 4)>
HWY_INLINE V Per4LaneBlockShuffle(hwy::SizeTag<0x88> /*idx_3210_tag*/, V v) {
  const DFromV<decltype(v)> d;
  return ConcatEven(d, v, v);
}

template <class V>
HWY_INLINE V Per4LaneBlockShuffle(hwy::SizeTag<0xA0> /*idx_3210_tag*/, V v) {
  return DupEven(v);
}

template <class V>
HWY_INLINE V Per4LaneBlockShuffle(hwy::SizeTag<0xB1> /*idx_3210_tag*/, V v) {
  const DFromV<decltype(v)> d;
  return Reverse2(d, v);
}

template <class V, HWY_IF_LANES_D(DFromV<V>, 4)>
HWY_INLINE V Per4LaneBlockShuffle(hwy::SizeTag<0xDD> /*idx_3210_tag*/, V v) {
  const DFromV<decltype(v)> d;
  return ConcatOdd(d, v, v);
}

template <class V>
HWY_INLINE V Per4LaneBlockShuffle(hwy::SizeTag<0xE4> /*idx_3210_tag*/, V v) {
  return v;
}

template <class V,
          HWY_IF_T_SIZE_ONE_OF_V(V, (1 << 1) | (1 << 2) |
                                        (HWY_HAVE_INTEGER64 ? (1 << 4) : 0))>
HWY_INLINE V Per4LaneBlockShuffle(hwy::SizeTag<0xEE> /*idx_3210_tag*/, V v) {
  const DFromV<decltype(v)> d;
  const auto vw = Per4LaneBlockShufCastToWide(
      hwy::IsFloatTag<TFromV<V>>(), hwy::SizeTag<sizeof(TFromV<V>)>(), v);
  return BitCast(d, DupOdd(vw));
}

template <class V>
HWY_INLINE V Per4LaneBlockShuffle(hwy::SizeTag<0xF5> /*idx_3210_tag*/, V v) {
  return DupOdd(v);
}

template <class V, HWY_IF_T_SIZE_V(V, 4)>
HWY_INLINE V Per4LaneBlockShuffle(hwy::SizeTag<0xFA> /*idx_3210_tag*/, V v) {
  const DFromV<decltype(v)> d;
  return InterleaveUpper(d, v, v);
}

template <size_t kIdx3210, class V>
HWY_INLINE V Per4LaneBlockShuffle(hwy::SizeTag<kIdx3210> idx_3210_tag, V v) {
  const DFromV<decltype(v)> d;
  return Per4LaneBlockShuffle(idx_3210_tag, hwy::SizeTag<sizeof(TFromV<V>)>(),
                              hwy::SizeTag<d.MaxBytes()>(), v);
}

}  // namespace detail
#endif  // HWY_TARGET != HWY_SCALAR

template <size_t kIdx3, size_t kIdx2, size_t kIdx1, size_t kIdx0, class V,
          HWY_IF_LANES_D(DFromV<V>, 1)>
HWY_API V Per4LaneBlockShuffle(V v) {
  static_assert(kIdx0 <= 3"kIdx0 <= 3 must be true");
  static_assert(kIdx1 <= 3"kIdx1 <= 3 must be true");
  static_assert(kIdx2 <= 3"kIdx2 <= 3 must be true");
  static_assert(kIdx3 <= 3"kIdx3 <= 3 must be true");

  return v;
}

#if HWY_TARGET != HWY_SCALAR
template <size_t kIdx3, size_t kIdx2, size_t kIdx1, size_t kIdx0, class V,
          HWY_IF_LANES_D(DFromV<V>, 2)>
HWY_API V Per4LaneBlockShuffle(V v) {
  static_assert(kIdx0 <= 3"kIdx0 <= 3 must be true");
  static_assert(kIdx1 <= 3"kIdx1 <= 3 must be true");
  static_assert(kIdx2 <= 3"kIdx2 <= 3 must be true");
  static_assert(kIdx3 <= 3"kIdx3 <= 3 must be true");

  constexpr bool isReverse2 = (kIdx0 == 1 || kIdx1 == 0) && (kIdx0 != kIdx1);
  constexpr size_t kPer2BlkIdx0 = (kIdx0 <= 1) ? kIdx0 : (isReverse2 ? 1 : 0);
  constexpr size_t kPer2BlkIdx1 = (kIdx1 <= 1) ? kIdx1 : (isReverse2 ? 0 : 1);

  constexpr size_t kIdx10 = (kPer2BlkIdx1 << 1) | kPer2BlkIdx0;
  static_assert(kIdx10 <= 3"kIdx10 <= 3 must be true");
  return detail::Per2LaneBlockShuffle(hwy::SizeTag<kIdx10>(), v);
}

template <size_t kIdx3, size_t kIdx2, size_t kIdx1, size_t kIdx0, class V,
          HWY_IF_LANES_GT_D(DFromV<V>, 2)>
HWY_API V Per4LaneBlockShuffle(V v) {
  static_assert(kIdx0 <= 3"kIdx0 <= 3 must be true");
  static_assert(kIdx1 <= 3"kIdx1 <= 3 must be true");
  static_assert(kIdx2 <= 3"kIdx2 <= 3 must be true");
  static_assert(kIdx3 <= 3"kIdx3 <= 3 must be true");

  constexpr size_t kIdx3210 =
      (kIdx3 << 6) | (kIdx2 << 4) | (kIdx1 << 2) | kIdx0;
  return detail::Per4LaneBlockShuffle(hwy::SizeTag<kIdx3210>(), v);
}
#endif

// ------------------------------ Blocks

template <class D>
HWY_API size_t Blocks(D d) {
  return (d.MaxBytes() <= 16) ? 1 : ((Lanes(d) * sizeof(TFromD<D>) + 15) / 16);
}

// ------------------------------ Block insert/extract/broadcast ops
#if (defined(HWY_NATIVE_BLK_INSERT_EXTRACT) == defined(HWY_TARGET_TOGGLE))
#ifdef HWY_NATIVE_BLK_INSERT_EXTRACT
#undef HWY_NATIVE_BLK_INSERT_EXTRACT
#else
#define HWY_NATIVE_BLK_INSERT_EXTRACT
#endif

template <int kBlockIdx, class V, HWY_IF_V_SIZE_LE_V(V, 16)>
HWY_API V InsertBlock(V /*v*/, V blk_to_insert) {
  static_assert(kBlockIdx == 0"Invalid block index");
  return blk_to_insert;
}

template <int kBlockIdx, class V, HWY_IF_V_SIZE_LE_V(V, 16)>
HWY_API V ExtractBlock(V v) {
  static_assert(kBlockIdx == 0"Invalid block index");
  return v;
}

template <int kBlockIdx, class V, HWY_IF_V_SIZE_LE_V(V, 16)>
HWY_API V BroadcastBlock(V v) {
  static_assert(kBlockIdx == 0"Invalid block index");
  return v;
}

#endif  // HWY_NATIVE_BLK_INSERT_EXTRACT

// ------------------------------ BroadcastLane
#if (defined(HWY_NATIVE_BROADCASTLANE) == defined(HWY_TARGET_TOGGLE))
#ifdef HWY_NATIVE_BROADCASTLANE
#undef HWY_NATIVE_BROADCASTLANE
#else
#define HWY_NATIVE_BROADCASTLANE
#endif

template <int kLane, class V, HWY_IF_V_SIZE_LE_V(V, 16)>
HWY_API V BroadcastLane(V v) {
  return Broadcast<kLane>(v);
}

#endif  // HWY_NATIVE_BROADCASTLANE

// ------------------------------ Slide1Up and Slide1Down
#if (defined(HWY_NATIVE_SLIDE1_UP_DOWN) == defined(HWY_TARGET_TOGGLE))
#ifdef HWY_NATIVE_SLIDE1_UP_DOWN
#undef HWY_NATIVE_SLIDE1_UP_DOWN
#else
#define HWY_NATIVE_SLIDE1_UP_DOWN
#endif

template <class D, HWY_IF_LANES_D(D, 1)>
HWY_API VFromD<D> Slide1Up(D d, VFromD<D> /*v*/) {
  return Zero(d);
}
template <class D, HWY_IF_LANES_D(D, 1)>
HWY_API VFromD<D> Slide1Down(D d, VFromD<D> /*v*/) {
  return Zero(d);
}

#if HWY_TARGET != HWY_SCALAR
template <class D, HWY_IF_V_SIZE_LE_D(D, 16), HWY_IF_LANES_GT_D(D, 1)>
HWY_API VFromD<D> Slide1Up(D d, VFromD<D> v) {
  return ShiftLeftLanes<1>(d, v);
}
template <class D, HWY_IF_V_SIZE_LE_D(D, 16), HWY_IF_LANES_GT_D(D, 1)>
HWY_API VFromD<D> Slide1Down(D d, VFromD<D> v) {
  return ShiftRightLanes<1>(d, v);
}
#endif  // HWY_TARGET != HWY_SCALAR

#endif  // HWY_NATIVE_SLIDE1_UP_DOWN

// ------------------------------ SlideUpBlocks

template <int kBlocks, class D, HWY_IF_V_SIZE_LE_D(D, 16)>
HWY_API VFromD<D> SlideUpBlocks(D /*d*/, VFromD<D> v) {
  static_assert(kBlocks == 0"kBlocks == 0 must be true");
  return v;
}

#if HWY_HAVE_SCALABLE || HWY_TARGET == HWY_SVE_256
template <int kBlocks, class D, HWY_IF_V_SIZE_GT_D(D, 16)>
HWY_API VFromD<D> SlideUpBlocks(D d, VFromD<D> v) {
  static_assert(0 <= kBlocks && static_cast<size_t>(kBlocks) < d.MaxBlocks(),
                "kBlocks must be between 0 and d.MaxBlocks() - 1");
  constexpr size_t kLanesPerBlock = 16 / sizeof(TFromD<D>);
  return SlideUpLanes(d, v, static_cast<size_t>(kBlocks) * kLanesPerBlock);
}
#endif

// ------------------------------ SlideDownBlocks

template <int kBlocks, class D, HWY_IF_V_SIZE_LE_D(D, 16)>
HWY_API VFromD<D> SlideDownBlocks(D /*d*/, VFromD<D> v) {
  static_assert(kBlocks == 0"kBlocks == 0 must be true");
  return v;
}

#if HWY_HAVE_SCALABLE || HWY_TARGET == HWY_SVE_256
template <int kBlocks, class D, HWY_IF_V_SIZE_GT_D(D, 16)>
HWY_API VFromD<D> SlideDownBlocks(D d, VFromD<D> v) {
  static_assert(0 <= kBlocks && static_cast<size_t>(kBlocks) < d.MaxBlocks(),
                "kBlocks must be between 0 and d.MaxBlocks() - 1");
  constexpr size_t kLanesPerBlock = 16 / sizeof(TFromD<D>);
  return SlideDownLanes(d, v, static_cast<size_t>(kBlocks) * kLanesPerBlock);
}
#endif

// ------------------------------ SumsOfAdjQuadAbsDiff

#if (defined(HWY_NATIVE_SUMS_OF_ADJ_QUAD_ABS_DIFF) == \
     defined(HWY_TARGET_TOGGLE))
#ifdef HWY_NATIVE_SUMS_OF_ADJ_QUAD_ABS_DIFF
#undef HWY_NATIVE_SUMS_OF_ADJ_QUAD_ABS_DIFF
#else
#define HWY_NATIVE_SUMS_OF_ADJ_QUAD_ABS_DIFF
#endif

#if HWY_TARGET != HWY_SCALAR
template <int kAOffset, int kBOffset, class V8, HWY_IF_UI8_D(DFromV<V8>)>
HWY_API Vec<RepartitionToWide<DFromV<V8>>> SumsOfAdjQuadAbsDiff(V8 a, V8 b) {
  static_assert(0 <= kAOffset && kAOffset <= 1,
                "kAOffset must be between 0 and 1");
  static_assert(0 <= kBOffset && kBOffset <= 3,
                "kBOffset must be between 0 and 3");
  using D8 = DFromV<V8>;
  const D8 d8;
  const RebindToUnsigned<decltype(d8)> du8;
  const RepartitionToWide<decltype(d8)> d16;
  const RepartitionToWide<decltype(du8)> du16;

  // Ensure that a is resized to a vector that has at least
  // HWY_MAX(Lanes(d8), size_t{8} << kAOffset) lanes for the interleave and
  // CombineShiftRightBytes operations below.
#if HWY_TARGET == HWY_RVV
  // On RVV targets, need to ensure that d8_interleave.Pow2() >= 0 is true
  // to ensure that Lanes(d8_interleave) >= 16 is true.

  // Lanes(d8_interleave) >= Lanes(d8) is guaranteed to be true on RVV
  // targets as d8_interleave.Pow2() >= d8.Pow2() is true.
  constexpr int kInterleavePow2 = HWY_MAX(d8.Pow2(), 0);
  const ScalableTag<TFromD<D8>, kInterleavePow2> d8_interleave;
#elif HWY_HAVE_SCALABLE || HWY_TARGET == HWY_SVE_256 || \
    HWY_TARGET == HWY_SVE2_128
  // On SVE targets, Lanes(d8_interleave) >= 16 and
  // Lanes(d8_interleave) >= Lanes(d8) are both already true as d8 is a SIMD
  // tag for a full u8/i8 vector on SVE.
  const D8 d8_interleave;
#else
  // On targets that use non-scalable vector types, Lanes(d8_interleave) is
  // equal to HWY_MAX(Lanes(d8), size_t{8} << kAOffset).
  constexpr size_t kInterleaveLanes =
      HWY_MAX(HWY_MAX_LANES_D(D8), size_t{8} << kAOffset);
  const FixedTag<TFromD<D8>, kInterleaveLanes> d8_interleave;
#endif

  // The ResizeBitCast operation below will resize a to a vector that has
  // at least HWY_MAX(Lanes(d8), size_t{8} << kAOffset) lanes for the
  // InterleaveLower, InterleaveUpper, and CombineShiftRightBytes operations
  // below.
  const auto a_to_interleave = ResizeBitCast(d8_interleave, a);

  const auto a_interleaved_lo =
      InterleaveLower(d8_interleave, a_to_interleave, a_to_interleave);
  const auto a_interleaved_hi =
      InterleaveUpper(d8_interleave, a_to_interleave, a_to_interleave);

  /* a01: { a[kAOffset*4+0], a[kAOffset*4+1], a[kAOffset*4+1], a[kAOffset*4+2],
            a[kAOffset*4+2], a[kAOffset*4+3], a[kAOffset*4+3], a[kAOffset*4+4],
            a[kAOffset*4+4], a[kAOffset*4+5], a[kAOffset*4+5], a[kAOffset*4+6],
            a[kAOffset*4+6], a[kAOffset*4+7], a[kAOffset*4+7], a[kAOffset*4+8] }
   */

  /* a23: { a[kAOffset*4+2], a[kAOffset*4+3], a[kAOffset*4+3], a[kAOffset*4+4],
            a[kAOffset*4+4], a[kAOffset*4+5], a[kAOffset*4+5], a[kAOffset*4+6],
            a[kAOffset*4+6], a[kAOffset*4+7], a[kAOffset*4+7], a[kAOffset*4+8],
            a[kAOffset*4+8], a[kAOffset*4+9], a[kAOffset*4+9], a[kAOffset*4+10]
     } */


  // a01 and a23 are resized back to V8 as only the first Lanes(d8) lanes of
  // the CombineShiftRightBytes are needed for the subsequent AbsDiff operations
  // and as a01 and a23 need to be the same vector type as b01 and b23 for the
  // AbsDiff operations below.
  const V8 a01 =
      ResizeBitCast(d8, CombineShiftRightBytes<kAOffset * 8 + 1>(
                            d8_interleave, a_interleaved_hi, a_interleaved_lo));
  const V8 a23 =
      ResizeBitCast(d8, CombineShiftRightBytes<kAOffset * 8 + 5>(
                            d8_interleave, a_interleaved_hi, a_interleaved_lo));

  /* b01: { b[kBOffset*4+0], b[kBOffset*4+1], b[kBOffset*4+0], b[kBOffset*4+1],
            b[kBOffset*4+0], b[kBOffset*4+1], b[kBOffset*4+0], b[kBOffset*4+1],
            b[kBOffset*4+0], b[kBOffset*4+1], b[kBOffset*4+0], b[kBOffset*4+1],
            b[kBOffset*4+0], b[kBOffset*4+1], b[kBOffset*4+0], b[kBOffset*4+1] }
   */

  /* b23: { b[kBOffset*4+2], b[kBOffset*4+3], b[kBOffset*4+2], b[kBOffset*4+3],
            b[kBOffset*4+2], b[kBOffset*4+3], b[kBOffset*4+2], b[kBOffset*4+3],
            b[kBOffset*4+2], b[kBOffset*4+3], b[kBOffset*4+2], b[kBOffset*4+3],
            b[kBOffset*4+2], b[kBOffset*4+3], b[kBOffset*4+2], b[kBOffset*4+3] }
   */

  const V8 b01 = BitCast(d8, Broadcast<kBOffset * 2>(BitCast(d16, b)));
  const V8 b23 = BitCast(d8, Broadcast<kBOffset * 2 + 1>(BitCast(d16, b)));

  const VFromD<decltype(du16)> absdiff_sum_01 =
      SumsOf2(BitCast(du8, AbsDiff(a01, b01)));
  const VFromD<decltype(du16)> absdiff_sum_23 =
      SumsOf2(BitCast(du8, AbsDiff(a23, b23)));
  return BitCast(d16, Add(absdiff_sum_01, absdiff_sum_23));
}
#endif  // HWY_TARGET != HWY_SCALAR

#endif  // HWY_NATIVE_SUMS_OF_ADJ_QUAD_ABS_DIFF

// ------------------------------ SumsOfShuffledQuadAbsDiff

#if (defined(HWY_NATIVE_SUMS_OF_SHUFFLED_QUAD_ABS_DIFF) == \
     defined(HWY_TARGET_TOGGLE))
#ifdef HWY_NATIVE_SUMS_OF_SHUFFLED_QUAD_ABS_DIFF
#undef HWY_NATIVE_SUMS_OF_SHUFFLED_QUAD_ABS_DIFF
#else
#define HWY_NATIVE_SUMS_OF_SHUFFLED_QUAD_ABS_DIFF
#endif

#if HWY_TARGET != HWY_SCALAR
template <int kIdx3, int kIdx2, int kIdx1, int kIdx0, class V8,
          HWY_IF_UI8_D(DFromV<V8>)>
HWY_API Vec<RepartitionToWide<DFromV<V8>>> SumsOfShuffledQuadAbsDiff(V8 a,
                                                                     V8 b) {
  static_assert(0 <= kIdx0 && kIdx0 <= 3"kIdx0 must be between 0 and 3");
  static_assert(0 <= kIdx1 && kIdx1 <= 3"kIdx1 must be between 0 and 3");
  static_assert(0 <= kIdx2 && kIdx2 <= 3"kIdx2 must be between 0 and 3");
  static_assert(0 <= kIdx3 && kIdx3 <= 3"kIdx3 must be between 0 and 3");

#if HWY_TARGET == HWY_RVV
  // On RVV, ensure that both vA and vB have a LMUL of at least 1/2 so that
  // both vA and vB can be bitcasted to a u32 vector.
  const detail::AdjustSimdTagToMinVecPow2<
      RepartitionToWideX2<DFromV<decltype(a)>>>
      d32;
  const RepartitionToNarrow<decltype(d32)> d16;
  const RepartitionToNarrow<decltype(d16)> d8;

  const auto vA = ResizeBitCast(d8, a);
  const auto vB = ResizeBitCast(d8, b);
#else
  const DFromV<decltype(a)> d8;
  const RepartitionToWide<decltype(d8)> d16;
  const RepartitionToWide<decltype(d16)> d32;

  const auto vA = a;
  const auto vB = b;
#endif

  const RebindToUnsigned<decltype(d8)> du8;

  const auto a_shuf =
      Per4LaneBlockShuffle<kIdx3, kIdx2, kIdx1, kIdx0>(BitCast(d32, vA));
  /* a0123_2345: { a_shuf[0], a_shuf[1], a_shuf[2], a_shuf[3],
                   a_shuf[2], a_shuf[3], a_shuf[4], a_shuf[5],
                   a_shuf[8], a_shuf[9], a_shuf[10], a_shuf[11],
                   a_shuf[10], a_shuf[11], a_shuf[12], a_shuf[13] } */

  /* a1234_3456: { a_shuf[1], a_shuf[2], a_shuf[3], a_shuf[4],
                   a_shuf[3], a_shuf[4], a_shuf[5], a_shuf[6],
                   a_shuf[9], a_shuf[10], a_shuf[11], a_shuf[12],
                   a_shuf[11], a_shuf[12], a_shuf[13], a_shuf[14] } */

#if HWY_HAVE_SCALABLE || HWY_TARGET == HWY_SVE_256 || HWY_TARGET == HWY_SVE2_128
  // On RVV/SVE targets, use Slide1Up/Slide1Down instead of
  // ShiftLeftBytes/ShiftRightBytes to avoid unnecessary zeroing out of any
  // lanes that are shifted into an adjacent 16-byte block as any lanes that are
  // shifted into an adjacent 16-byte block by Slide1Up/Slide1Down will be
  // replaced by the OddEven operation.
  const auto a_0123_2345 = BitCast(
      d8, OddEven(BitCast(d32, Slide1Up(d16, BitCast(d16, a_shuf))), a_shuf));
  const auto a_1234_3456 =
      BitCast(d8, OddEven(BitCast(d32, Slide1Up(d8, BitCast(d8, a_shuf))),
                          BitCast(d32, Slide1Down(d8, BitCast(d8, a_shuf)))));
#else
  const auto a_0123_2345 =
      BitCast(d8, OddEven(ShiftLeftBytes<2>(d32, a_shuf), a_shuf));
  const auto a_1234_3456 = BitCast(
      d8,
      OddEven(ShiftLeftBytes<1>(d32, a_shuf), ShiftRightBytes<1>(d32, a_shuf)));
#endif

  auto even_sums = SumsOf4(BitCast(du8, AbsDiff(a_0123_2345, vB)));
  auto odd_sums = SumsOf4(BitCast(du8, AbsDiff(a_1234_3456, vB)));

#if HWY_IS_LITTLE_ENDIAN
  odd_sums = ShiftLeft<16>(odd_sums);
#else
  even_sums = ShiftLeft<16>(even_sums);
#endif

  const auto sums = OddEven(BitCast(d16, odd_sums), BitCast(d16, even_sums));

#if HWY_TARGET == HWY_RVV
  return ResizeBitCast(RepartitionToWide<DFromV<V8>>(), sums);
#else
  return sums;
#endif
}
#endif  // HWY_TARGET != HWY_SCALAR

#endif  // HWY_NATIVE_SUMS_OF_SHUFFLED_QUAD_ABS_DIFF

// ================================================== Operator wrapper

// SVE* and RVV currently cannot define operators and have already defined
// (only) the corresponding functions such as Add.
#if (defined(HWY_NATIVE_OPERATOR_REPLACEMENTS) == defined(HWY_TARGET_TOGGLE))
#ifdef HWY_NATIVE_OPERATOR_REPLACEMENTS
#undef HWY_NATIVE_OPERATOR_REPLACEMENTS
#else
#define HWY_NATIVE_OPERATOR_REPLACEMENTS
#endif

template <class V>
HWY_API V Add(V a, V b) {
  return a + b;
}
template <class V>
HWY_API V Sub(V a, V b) {
  return a - b;
}

template <class V>
HWY_API V Mul(V a, V b) {
  return a * b;
}
template <class V>
HWY_API V Div(V a, V b) {
  return a / b;
}
template <class V>
HWY_API V Mod(V a, V b) {
  return a % b;
}

template <class V>
V Shl(V a, V b) {
  return a << b;
}
template <class V>
V Shr(V a, V b) {
  return a >> b;
}

template <class V>
HWY_API auto Eq(V a, V b) -> decltype(a == b) {
  return a == b;
}
template <class V>
HWY_API auto Ne(V a, V b) -> decltype(a == b) {
  return a != b;
}
template <class V>
HWY_API auto Lt(V a, V b) -> decltype(a == b) {
  return a < b;
}

template <class V>
HWY_API auto Gt(V a, V b) -> decltype(a == b) {
  return a > b;
}
template <class V>
HWY_API auto Ge(V a, V b) -> decltype(a == b) {
  return a >= b;
}

template <class V>
HWY_API auto Le(V a, V b) -> decltype(a == b) {
  return a <= b;
}

#endif  // HWY_NATIVE_OPERATOR_REPLACEMENTS

// NOLINTNEXTLINE(google-readability-namespace-comments)
}  // namespace HWY_NAMESPACE
}  // namespace hwy
HWY_AFTER_NAMESPACE();

Messung V0.5 in Prozent
C=77 H=98 G=88

¤ Dauer der Verarbeitung: 0.383 Sekunden  (vorverarbeitet am  2026-06-06) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.