Skip to content

Commit 7101cd0

Browse files
Merge pull request #3544 from LittlePyx:fix/vqsort-network-signed-zeros
PiperOrigin-RevId: 1019162426
2 parents 886c883 + 7aa2787 commit 7101cd0

5 files changed

Lines changed: 51 additions & 13 deletions

File tree

‎hwy/contrib/sort/shared-inl.h‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,10 @@ static_assert(SortConstants::MaxBufBytes<2>(64) <= 1664, "Unexpectedly high");
165165
namespace hwy {
166166
namespace HWY_NAMESPACE {
167167

168+
namespace detail {
169+
enum class PairLayout { kIndependent, kMirrored };
170+
} // namespace detail
171+
168172
// Default tag / vector width selector.
169173
#if HWY_TARGET == HWY_RVV
170174
// Use LMUL = 1/2; for SEW=64 this ends up emulated via VSETVLI.

‎hwy/contrib/sort/sort_unit_test.cc‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,27 @@ static HWY_NOINLINE void TestBaseCase() {
309309
TestBaseCase01<Traits>();
310310
}
311311

312+
template <class Traits>
313+
static HWY_NOINLINE void TestBaseCaseSignedZero() {
314+
using T = typename Traits::LaneType;
315+
const SortTag<T> d;
316+
const size_t num = SortConstants::BaseCaseNumLanes(Lanes(d));
317+
auto keys = hwy::AllocateAligned<T>(num);
318+
auto buf = hwy::AllocateAligned<T>(SortConstants::BufNum<T>(Lanes(d)));
319+
HWY_ASSERT(keys && buf);
320+
// Two keys exercise one compare-exchange. The full network also compares
321+
// lanes within the same vector; place the negative zero on either side.
322+
for (size_t len : {size_t{2}, num}) {
323+
for (size_t pos = 0; pos < len; ++pos) {
324+
for (size_t i = 0; i < len; ++i) keys[i] = i == pos ? -T{0} : T{0};
325+
detail::BaseCase(d, SharedTraits<Traits>(), keys.get(), len, buf.get());
326+
size_t count = 0;
327+
for (size_t i = 0; i < len; ++i) count += ScalarSignBit(keys[i]);
328+
HWY_ASSERT_EQ(size_t{1}, count);
329+
}
330+
}
331+
}
332+
312333
HWY_NOINLINE void TestAllBaseCase() {
313334
// Workaround for stack overflow on MSVC debug.
314335
#if defined(_MSC_VER)
@@ -317,6 +338,10 @@ HWY_NOINLINE void TestAllBaseCase() {
317338

318339
TestBaseCase<TraitsLane<OrderAscending<int32_t> > >();
319340
TestBaseCase<TraitsLane<OtherOrder<int64_t> > >();
341+
TestBaseCaseSignedZero<TraitsLane<OrderAscending<float>>>();
342+
#if HWY_HAVE_FLOAT64
343+
TestBaseCaseSignedZero<TraitsLane<detail::OrderDescending<double>>>();
344+
#endif
320345
#if !HAVE_INTEL
321346
TestBaseCase<Traits128<OrderAscending128> >();
322347
TestBaseCase<Traits128<OrderDescending128> >();

‎hwy/contrib/sort/sorting_networks-inl.h‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ struct SharedTraits : public Base {
5151
HWY_INLINE Vec<D> SortPairsDistance2(D d, Vec<D> v) const {
5252
const Base* base = static_cast<const Base*>(this);
5353
Vec<D> swapped = base->SwapAdjacentPairs(d, v);
54-
base->Sort2(d, v, swapped);
54+
base->template Sort2<PairLayout::kMirrored>(d, v, swapped);
5555
return base->OddEvenPairs(d, swapped, v);
5656
}
5757

@@ -60,7 +60,7 @@ struct SharedTraits : public Base {
6060
HWY_INLINE Vec<D> SortPairsReverse8(D d, Vec<D> v) const {
6161
const Base* base = static_cast<const Base*>(this);
6262
Vec<D> swapped = base->ReverseKeys8(d, v);
63-
base->Sort2(d, v, swapped);
63+
base->template Sort2<PairLayout::kMirrored>(d, v, swapped);
6464
return base->OddEvenQuads(d, swapped, v);
6565
}
6666

@@ -70,7 +70,7 @@ struct SharedTraits : public Base {
7070
const Base* base = static_cast<const Base*>(this);
7171
static_assert(Constants::kMaxCols <= 16, "Need actual Reverse16");
7272
Vec<D> swapped = base->ReverseKeys(d, v);
73-
base->Sort2(d, v, swapped);
73+
base->template Sort2<PairLayout::kMirrored>(d, v, swapped);
7474
return ConcatUpperLower(d, swapped, v); // 8 = half of the vector
7575
}
7676
};

‎hwy/contrib/sort/traits-inl.h‎

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -550,23 +550,32 @@ struct TraitsLane : public Base {
550550
// according to Base.
551551
// Corresponds to a conditional swap, which is one "node" of a sorting
552552
// network. Min/Max are cheaper than compare + blend at least for integers.
553-
template <class D>
553+
template <PairLayout kLayout = PairLayout::kIndependent, class D>
554554
HWY_INLINE void Sort2(D d, Vec<D>& a, Vec<D>& b) const {
555555
const Base* base = static_cast<const Base*>(this);
556556

557557
const Vec<D> a_copy = a;
558-
// Prior to AVX3, there is no native 64-bit Min/Max, so they compile to 4
559-
// instructions. We can reduce it to a compare + 2 IfThenElse.
558+
// Prior to AVX3 there is no native 64-bit integer Min/Max. Use a compare
559+
// and two blends, except for mirrored floats: selecting opposite inputs
560+
// on a tie there would duplicate one zero after the lanes are merged.
560561
#if HWY_AVX3 < HWY_TARGET && HWY_TARGET <= HWY_SSSE3
561-
if (sizeof(TFromD<D>) == 8) {
562+
if (sizeof(TFromD<D>) == 8 &&
563+
!(kLayout == PairLayout::kMirrored && IsFloat<TFromD<D>>())) {
562564
const Mask<D> cmp = base->Compare(d, a, b);
563565
a = IfThenElse(cmp, a, b);
564566
b = IfThenElse(cmp, b, a_copy);
565567
return;
566568
}
567569
#endif
568570
a = base->First(d, a, b);
569-
b = base->Last(d, a_copy, b);
571+
// Some floating-point Min/Max instructions both choose the second operand
572+
// for equal zeros. Keep one of each input. Mirrored lanes already compare
573+
// the same pair in opposite orders before selecting one result per lane.
574+
if constexpr (kLayout == PairLayout::kIndependent && IsFloat<TFromD<D>>()) {
575+
b = base->Last(d, b, a_copy);
576+
} else {
577+
b = base->Last(d, a_copy, b);
578+
}
570579
}
571580

572581
// Conditionally swaps even-numbered lanes with their odd-numbered neighbor.
@@ -580,7 +589,7 @@ struct TraitsLane : public Base {
580589
const Vec<D> cmp = VecFromMask(d, base->Compare(d, v, swapped));
581590
return IfVecThenElse(DupOdd(cmp), swapped, v);
582591
#else
583-
Sort2(d, v, swapped);
592+
Sort2<PairLayout::kMirrored>(d, v, swapped);
584593
return base->OddEvenKeys(swapped, v);
585594
#endif
586595
}
@@ -590,7 +599,7 @@ struct TraitsLane : public Base {
590599
HWY_INLINE Vec<D> SortPairsDistance1(D d, Vec<D> v) const {
591600
const Base* base = static_cast<const Base*>(this);
592601
Vec<D> swapped = base->ReverseKeys2(d, v);
593-
Sort2(d, v, swapped);
602+
Sort2<PairLayout::kMirrored>(d, v, swapped);
594603
return base->OddEvenKeys(swapped, v);
595604
}
596605

@@ -599,7 +608,7 @@ struct TraitsLane : public Base {
599608
HWY_INLINE Vec<D> SortPairsReverse4(D d, Vec<D> v) const {
600609
const Base* base = static_cast<const Base*>(this);
601610
Vec<D> swapped = base->ReverseKeys4(d, v);
602-
Sort2(d, v, swapped);
611+
Sort2<PairLayout::kMirrored>(d, v, swapped);
603612
return base->OddEvenPairs(d, swapped, v);
604613
}
605614

@@ -610,7 +619,7 @@ struct TraitsLane : public Base {
610619
Vec<D> swapped = base->SwapAdjacentQuads(d, v);
611620
// Only used in Merge16, so this will not be used on AVX2 (which only has 4
612621
// u64 lanes), so skip the above optimization for 64-bit AVX2.
613-
Sort2(d, v, swapped);
622+
Sort2<PairLayout::kMirrored>(d, v, swapped);
614623
return base->OddEvenQuads(d, swapped, v);
615624
}
616625
};

‎hwy/contrib/sort/traits128-inl.h‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -494,7 +494,7 @@ struct Traits128 : public Base {
494494
return v;
495495
}
496496

497-
template <class D, HWY_IF_U64_D(D)>
497+
template <PairLayout = PairLayout::kIndependent, class D, HWY_IF_U64_D(D)>
498498
HWY_INLINE void Sort2(D d, Vec<D>& a, Vec<D>& b) const {
499499
const Base* base = static_cast<const Base*>(this);
500500

0 commit comments

Comments
 (0)