From 10bdaeeb2043a038f4e3fc773ac32f286e5d47a5 Mon Sep 17 00:00:00 2001 From: Minh Vu Date: Tue, 10 Nov 2026 13:40:46 +0100 Subject: [PATCH 1/2] contrib/algo: add FindLast and FindLastIf --- g3doc/op_wishlist.md | 3 +- hwy/contrib/algo/find-inl.h | 61 +++++++++++++++++++++++++++++++++++ hwy/contrib/algo/find_test.cc | 31 ++++++++++++++++++ 3 files changed, 93 insertions(+), 2 deletions(-) diff --git a/g3doc/op_wishlist.md b/g3doc/op_wishlist.md index 3de0c8f49c..be4b0720bc 100644 --- a/g3doc/op_wishlist.md +++ b/g3doc/op_wishlist.md @@ -36,8 +36,6 @@ fmod, nexttoward * In-place Remove / RemoveIf, like CopyIf, but in-place. -* FindLast / FindLastIf (in find-inl.h) - can use FindLastTrue. - * index-returning Mismatch(d, a, b, count) (in find-inl.h) - like EqualSpan, but returns the first mismatched index. @@ -174,6 +172,7 @@ For SVE (svld1sb_u32)+WASM? Compiler can probably already fuse. * ~~Document Reduce/Min NaN behavior~~ * ~~IndexOfMin/Max~~ (algo) * ~~AllOf / AnyOf / NoneOf~~ (algo) +* ~~FindLast / FindLastIf~~ (algo) * ~~EqualSpan~~ (algo) * ~~ReverseSpan~~ (algo) * ~~NEON dot product~~ diff --git a/hwy/contrib/algo/find-inl.h b/hwy/contrib/algo/find-inl.h index ae7371bbd7..10480efa33 100644 --- a/hwy/contrib/algo/find-inl.h +++ b/hwy/contrib/algo/find-inl.h @@ -31,6 +31,20 @@ HWY_BEFORE_NAMESPACE(); namespace hwy { namespace HWY_NAMESPACE { +namespace detail { + +template +struct FindEqualTo { + T value; + + template + Mask operator()(D d, V v) const { + return Eq(Set(d, value), v); + } +}; + +} // namespace detail + // Returns index of the first element equal to `value` in `in[0, count)`, or // `count` if not found. template > @@ -108,6 +122,53 @@ size_t FindIf(D d, const T* HWY_RESTRICT in, size_t count, const Func& func) { return count; // not found } +// Returns index of the last element in `in[0, count)` for which `func(d, vec)` +// returns true, or `count` if no element matches. +template > +size_t FindLastIf(D d, const T* HWY_RESTRICT in, size_t count, + const Func& func) { + HWY_LANES_CONSTEXPR size_t N = Lanes(d); + size_t remaining = count; + + // Visit pairs of vectors from the end so the highest matching index wins. + if (HWY_LIKELY(remaining >= 2 * N)) { + for (; remaining >= 2 * N; remaining -= 2 * N) { + const size_t i = remaining - 2 * N; + const Mask match0 = func(d, LoadU(d, in + i)); + const Mask match1 = func(d, LoadU(d, in + i + N)); + if (AllFalse(d, Or(match0, match1))) continue; + const intptr_t pos1 = FindLastTrue(d, match1); + if (pos1 >= 0) return i + N + static_cast(pos1); + return i + FindKnownLastTrue(d, match0); + } + } + + if (remaining >= N) { + const size_t i = remaining - N; + const Mask match = func(d, LoadU(d, in + i)); + const intptr_t pos = FindLastTrue(d, match); + if (pos >= 0) return i + static_cast(pos); + remaining -= N; + } + + HWY_DASSERT(remaining < N); + if (remaining != 0) { + const Mask valid = FirstN(d, remaining); + const Mask match = And(valid, func(d, LoadN(d, in, remaining))); + const intptr_t pos = FindLastTrue(d, match); + if (pos >= 0) return static_cast(pos); + } + + return count; // not found +} + +// Returns index of the last element equal to `value` in `in[0, count)`, or +// `count` if not found. +template > +size_t FindLast(D d, T value, const T* HWY_RESTRICT in, size_t count) { + return FindLastIf(d, in, count, detail::FindEqualTo{value}); +} + // AnyOf/AllOf/NoneOf unroll 4x, matching AllUnique below rather than the 2x in // Find/FindIf: none of them needs the position of a match, so an iteration is // just the loads, the predicate and one mask test, with no loop-carried state. diff --git a/hwy/contrib/algo/find_test.cc b/hwy/contrib/algo/find_test.cc index 4c4e310173..a9f98eb16a 100644 --- a/hwy/contrib/algo/find_test.cc +++ b/hwy/contrib/algo/find_test.cc @@ -89,6 +89,15 @@ struct TestFind { // For each position, search for that element (which we know is there) for (size_t pos = 0; pos < count; ++pos) { const size_t actual = Find(d, in[pos], in, count); + const size_t actual_last = FindLast(d, in[pos], in, count); + size_t expected_last = count; + for (size_t i = count; i != 0; --i) { + if (IsEqual(in[i - 1], in[pos])) { + expected_last = i - 1; + break; + } + } + HWY_ASSERT_EQ(expected_last, actual_last); // We may have found an earlier occurrence of the same value; ensure the // value is the same, and that it is the first. @@ -113,6 +122,8 @@ struct TestFind { // Also search for values we know not to be present (out of range) HWY_ASSERT_EQ(count, Find(d, ConvertScalarTo(9), in, count)); HWY_ASSERT_EQ(count, Find(d, ConvertScalarTo(-9), in, count)); + HWY_ASSERT_EQ(count, FindLast(d, ConvertScalarTo(9), in, count)); + HWY_ASSERT_EQ(count, FindLast(d, ConvertScalarTo(-9), in, count)); } }; @@ -148,6 +159,7 @@ struct TestFindIf { return Gt(v, Set(d2, ConvertScalarTo(val))); }; const size_t actual = FindIf(d, in, count, greater); + const size_t actual_last = FindLastIf(d, in, count, greater); found_any |= actual < count; not_found_any |= actual == count; @@ -163,6 +175,15 @@ struct TestFindIf { 0, count); HWY_ASSERT(false); } + + size_t expected_last = count; + for (size_t i = count; i != 0; --i) { + if (in[i - 1] > ConvertScalarTo(val)) { + expected_last = i - 1; + break; + } + } + HWY_ASSERT_EQ(expected_last, actual_last); } // We will always not-find something due to val=9. @@ -172,6 +193,16 @@ struct TestFindIf { if (count != 0 && in[0] != ConvertScalarTo(0)) { HWY_ASSERT(found_any); } + + const auto always = [](const auto /*d2*/, const auto v) HWY_ATTR { + return Eq(v, v); + }; + const auto never = [](const auto /*d2*/, const auto v) HWY_ATTR { + return Ne(v, v); + }; + HWY_ASSERT_EQ(count == 0 ? count : count - 1, + FindLastIf(d, in, count, always)); + HWY_ASSERT_EQ(count, FindLastIf(d, in, count, never)); } }; From 11c5e1b6f3456b1cfbd9a3cfcb5183c1365cba06 Mon Sep 17 00:00:00 2001 From: Minh Vu Date: Wed, 11 Nov 2026 12:38:26 +0100 Subject: [PATCH 2/2] contrib/algo: use SIMD lambda for FindLast --- hwy/contrib/algo/find-inl.h | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/hwy/contrib/algo/find-inl.h b/hwy/contrib/algo/find-inl.h index 10480efa33..692a733c99 100644 --- a/hwy/contrib/algo/find-inl.h +++ b/hwy/contrib/algo/find-inl.h @@ -31,20 +31,6 @@ HWY_BEFORE_NAMESPACE(); namespace hwy { namespace HWY_NAMESPACE { -namespace detail { - -template -struct FindEqualTo { - T value; - - template - Mask operator()(D d, V v) const { - return Eq(Set(d, value), v); - } -}; - -} // namespace detail - // Returns index of the first element equal to `value` in `in[0, count)`, or // `count` if not found. template > @@ -166,7 +152,10 @@ size_t FindLastIf(D d, const T* HWY_RESTRICT in, size_t count, // `count` if not found. template > size_t FindLast(D d, T value, const T* HWY_RESTRICT in, size_t count) { - return FindLastIf(d, in, count, detail::FindEqualTo{value}); + const auto equal = [value](const auto d2, const auto v) HWY_ATTR { + return Eq(Set(d2, value), v); + }; + return FindLastIf(d, in, count, equal); } // AnyOf/AllOf/NoneOf unroll 4x, matching AllUnique below rather than the 2x in