Skip to content

Commit 36569ad

Browse files
Made first_true a new format callable (#2076)
1 parent 390c141 commit 36569ad

File tree

5 files changed

+258
-190
lines changed

5 files changed

+258
-190
lines changed

include/eve/module/core/regular/first_true.hpp

+73-57
Original file line numberDiff line numberDiff line change
@@ -11,71 +11,87 @@
1111

1212
namespace eve
1313
{
14-
//TODO DOC
15-
//================================================================================================
16-
//! @addtogroup core_reduction
17-
//! @{
18-
//! @var first_true
19-
//! @brief A function to find a first true value, if there is one.
20-
//!
21-
//! @code
22-
//! #include <eve/module/core.hpp>
23-
//! @endcode
24-
//!
25-
//! ## Should you check for any?
26-
//!
27-
//! The function is considering the case when nothing is set to be likely,
28-
//! checking for eve::any before hand is not going to be helpful.
29-
//! At the moment there isn't a function that would do it otherwise.
30-
//!
31-
//! ## What if I know there is a match?
32-
//! We would recommend `*eve::first_true(m)` - this is likely to trigger
33-
//! compiler optimizations, based on it being UB otherwise.
34-
//!
35-
//! @groupheader{Callable Signatures}
36-
//!
37-
//! @code
38-
//! template <logical_simd_value L>
39-
//! std::optional<std::ptrdiff_t> first_true(L m); // (1)
40-
//!
41-
//! template <logical_simd_value L>
42-
//! std::optional<std::ptrdiff_t> first_true(top_bits<L> m); // (2)
43-
//!
44-
//! template <relative_conditional_expr C, logical_simd_value L>
45-
//! std::optional<std::ptrdiff_t> first_true[C ignore](L m); // (3)
46-
//!
47-
//! std::optional<std::ptrdiff_t> first_true(bool m); // (4)
48-
//! @endcode
49-
//!
50-
//! **Parameters**
51-
//!
52-
//! * m - logical_value or top_bits where to find first true value
53-
//! * ignore - ignored elements are considered false. Only supported for
54-
//! `logical_simd_value`
55-
//!
56-
//! **Return value**
57-
//!
58-
//! Returns `std::nullopt` if eve::none(m).
59-
//! Otherwise returns 0 based index.
60-
//!
61-
//! @groupheader{Example}
62-
//!
63-
//! @godbolt{doc/core/first_true.cpp}
64-
//================================================================================================
65-
EVE_MAKE_CALLABLE(first_true_, first_true);
66-
//================================================================================================
67-
//! @}
68-
//================================================================================================
14+
template<typename Options>
15+
struct first_true_t : conditional_callable<first_true_t, Options>
16+
{
17+
template<relaxed_logical_value T>
18+
EVE_FORCEINLINE std::optional<std::ptrdiff_t> operator()(T v) const noexcept
19+
{
20+
static_assert(detail::validate_mask_for<decltype(this->options()), T>(),
21+
"[eve::first_true] - Cannot use a relative conditional expression or a simd value to mask a scalar value");
22+
23+
return EVE_DISPATCH_CALL(v);
24+
}
25+
26+
template<logical_simd_value T>
27+
EVE_FORCEINLINE std::optional<std::ptrdiff_t> operator()(top_bits<T> v) const noexcept
28+
{
29+
return EVE_DISPATCH_CALL(v);
30+
}
31+
32+
EVE_CALLABLE_OBJECT(first_true_t, first_true_);
33+
};
34+
35+
//================================================================================================
36+
//! @addtogroup core_reduction
37+
//! @{
38+
//! @var first_true
39+
//! @brief Returns the index of the first element in the input which evaluates to `true`, if there is one.
40+
//!
41+
//! @code
42+
//! #include <eve/module/core.hpp>
43+
//! @endcode
44+
//!
45+
//! @groupheader{Callable Signatures}
46+
//!
47+
//! @code
48+
//! template <relaxed_logical_value L>
49+
//! std::optional<std::ptrdiff_t> first_true(L m) noexcept; // 1
50+
//!
51+
//! template <logical_simd_value L>
52+
//! std::optional<std::ptrdiff_t> first_true(top_bits<L> m) noexcept; // 1
53+
//!
54+
//! // lane masking
55+
//! std::optional<std::ptrdiff_t> first_true[conditional_expr auto c](/* any of the above */) noexcept; // 2
56+
//! std::optional<std::ptrdiff_t> first_true[logical_value auto c](/* any of the above */) noexcept; // 2
57+
//!
58+
//! @endcode
59+
//!
60+
//! **Parameters**
61+
//!
62+
//! * m - logical_value or top_bits where to find first true value
63+
//!
64+
//! **Return value**
65+
//!
66+
//! 1. An `std::optional` containing the index of the first element which evaluates to `true`,
67+
//! `std::nullopt` if there are none.
68+
//! 2. Same as 1. but masked elements are ignored during the search.
69+
//!
70+
//! ## Should you check for any?
71+
//!
72+
//! The function is considering the case when nothing is set to be likely,
73+
//! checking for eve::any before hand is not going to be helpful.
74+
//! At the moment there isn't a function that would do it otherwise.
75+
//!
76+
//! ## What if I know there is a match?
77+
//! We would recommend `*eve::first_true(m)` - this is likely to trigger
78+
//! compiler optimizations, based on it being UB otherwise.
79+
//!
80+
//! @groupheader{Example}
81+
//! @godbolt{doc/core/first_true.cpp}
82+
//================================================================================================
83+
inline constexpr auto first_true = functor<first_true_t>;
84+
//================================================================================================
85+
//! @}
86+
//================================================================================================
6987
}
7088

71-
#include <eve/arch.hpp>
7289
#include <eve/module/core/regular/impl/first_true.hpp>
7390

7491
#if defined(EVE_INCLUDE_ARM_NEON_HEADER)
7592
# include <eve/module/core/regular/impl/simd/arm/neon/first_true.hpp>
7693
#endif
7794

78-
7995
#if defined(EVE_INCLUDE_ARM_SVE_HEADER)
8096
# include <eve/module/core/regular/impl/simd/arm/sve/first_true.hpp>
8197
#endif

include/eve/module/core/regular/impl/first_true.hpp

+57-36
Original file line numberDiff line numberDiff line change
@@ -44,66 +44,87 @@ EVE_FORCEINLINE std::ptrdiff_t
4444
}
4545
}
4646

47-
template<logical_simd_value T, relative_conditional_expr C>
47+
template<callable_options O, logical_value T>
4848
EVE_FORCEINLINE std::optional<std::ptrdiff_t>
49-
first_true_(EVE_SUPPORTS(cpu_), C const &cond, T const &v) noexcept
49+
first_true_(EVE_REQUIRES(cpu_), O const& opts, T const& v) noexcept
5050
{
51+
using C = rbr::result::fetch_t<condition_key, O>;
52+
auto cond = opts[condition_key];
53+
54+
if constexpr (scalar_value<T>) return first_true[cond](v.value());
55+
else
5156
if constexpr( C::is_complete && !C::is_inverted ) return {};
5257
else if constexpr( has_emulated_abi_v<T> )
5358
{
54-
std::ptrdiff_t first = cond.offset(eve::as<T> {});
55-
std::ptrdiff_t last = first + cond.count(eve::as<T> {});
56-
57-
while( first != last )
59+
if constexpr (relative_conditional_expr<C>)
5860
{
59-
if( v.get(first) ) return first;
60-
++first;
61+
std::ptrdiff_t first = cond.offset(eve::as<T> {});
62+
std::ptrdiff_t last = first + cond.count(eve::as<T> {});
63+
constexpr std::ptrdiff_t size = T::size();
64+
EVE_ASSUME((first >= 0) && (first <= last) && (last <= size));
65+
66+
while( first != last )
67+
{
68+
if( v.get(first) ) return first;
69+
++first;
70+
}
71+
72+
return {};
6173
}
74+
else
75+
{
76+
auto mask = expand_mask(cond, as(v));
6277

63-
return {};
64-
}
65-
// This is pretty good for aggreagted as well.
66-
else if constexpr( !top_bits<T>::is_cheap )
67-
{
68-
// No ignore, we might luck out even if some elements should not be counted.
69-
if( !eve::any(v) ) return {};
78+
for (std::ptrdiff_t i = 0; i < v.size(); ++i)
79+
if (v.get(i) && mask.get(i))
80+
return i;
7081

71-
top_bits mmask {v, cond};
72-
if constexpr( C::is_complete ) return first_true_guaranteed(mmask);
73-
else return first_true(mmask);
82+
return std::nullopt;
83+
}
7484
}
7585
else
7686
{
77-
top_bits mmask {v, cond};
78-
return first_true(mmask);
79-
}
80-
}
87+
// This is pretty good for aggreagted as well.
88+
if constexpr( !top_bits<T>::is_cheap )
89+
{
90+
// No ignore, we might luck out even if some elements should not be counted.
91+
if( !eve::any(v) ) return {};
8192

82-
EVE_FORCEINLINE std::optional<std::ptrdiff_t>
83-
first_true_(EVE_SUPPORTS(cpu_), bool v) noexcept
84-
{
85-
if( !v ) return std::nullopt;
86-
return 0;
93+
// any + ignore_none
94+
if constexpr (C::is_complete)
95+
{
96+
return first_true_guaranteed(top_bits{v, cond});
97+
}
98+
}
99+
100+
if constexpr (relative_conditional_expr<C>) return first_true(top_bits{v, cond});
101+
else return first_true[cond](top_bits{v});
102+
}
87103
}
88104

89-
template<value T>
105+
template<callable_options O>
90106
EVE_FORCEINLINE std::optional<std::ptrdiff_t>
91-
first_true_(EVE_SUPPORTS(cpu_), T const &v) noexcept
107+
first_true_(EVE_REQUIRES(cpu_), O const& opts, bool v) noexcept
92108
{
93-
if constexpr( !scalar_value<T> ) return eve::first_true[ignore_none](v);
94-
else
109+
if constexpr (match_option<condition_key, O, ignore_none_>)
95110
{
96111
if( !v ) return std::nullopt;
97112
return 0;
98113
}
114+
else
115+
{
116+
return opts[condition_key].mask(as(v)) && v ? std::make_optional(0) : std::nullopt;
117+
}
99118
}
100119

101-
template<logical_simd_value Logical>
102-
EVE_FORCEINLINE std::optional<std::ptrdiff_t>
103-
first_true_(EVE_SUPPORTS(cpu_), top_bits<Logical> mmask) noexcept
120+
template<callable_options O, logical_simd_value Logical>
121+
EVE_FORCEINLINE std::optional<std::ptrdiff_t> first_true_(EVE_REQUIRES(cpu_), O const& opts, top_bits<Logical> mmask) noexcept
104122
{
123+
if constexpr (!match_option<condition_key, O, ignore_none_>)
124+
{
125+
mmask &= top_bits{expand_mask(opts[condition_key], as<Logical>{})};
126+
}
105127
if( !any(mmask) ) return {};
106128
return first_true_guaranteed(mmask);
107129
}
108-
109-
} // namespace eve
130+
}

include/eve/module/core/regular/impl/simd/arm/neon/first_true.hpp

+14-5
Original file line numberDiff line numberDiff line change
@@ -14,20 +14,29 @@
1414
namespace eve::detail
1515
{
1616

17-
template<arithmetic_scalar_value T, typename N, relative_conditional_expr C>
17+
template<callable_options O, arithmetic_scalar_value T, typename N>
1818
EVE_FORCEINLINE std::optional<std::ptrdiff_t>
19-
first_true_(EVE_SUPPORTS(neon128_),
20-
C const &cond,
19+
first_true_(EVE_REQUIRES(neon128_),
20+
O const& opts,
2121
logical<wide<T, N>> const &v0) noexcept requires std::same_as<abi_t<T, N>, arm_128_>
2222
{
23+
using C = rbr::result::fetch_t<condition_key, O>;
24+
auto cx = opts[condition_key];
2325
if constexpr( C::is_complete && !C::is_inverted ) return std::nullopt;
2426
// we still have to convert down here, so we can do it before ignore.
2527
else if constexpr( eve::current_api < eve::asimd && sizeof(T) >= 2 )
2628
{
2729
using half_e_t = make_integer_t<sizeof(T) / 2, unsigned>;
2830
auto halved = eve::convert(v0, eve::as<eve::logical<half_e_t>> {});
29-
return eve::first_true[cond](halved);
31+
if constexpr(relative_conditional_expr<C>) return first_true[cx](halved);
32+
else
33+
{
34+
// When calling first_true with a non-relative mask, we need to expand the mask, convert it
35+
// then call first_true again with a full logical mask.
36+
auto m = convert(expand_mask(cx, as(v0)), as<logical<half_e_t>> {});
37+
return eve::first_true[m](halved);
38+
}
3039
}
31-
else return first_true_(EVE_RETARGET(cpu_), cond, v0);
40+
else return first_true.behavior(cpu_{}, opts, v0);
3241
}
3342
}

include/eve/module/core/regular/impl/simd/arm/sve/first_true.hpp

+24-18
Original file line numberDiff line numberDiff line change
@@ -13,24 +13,37 @@ namespace eve::detail
1313
// There are some explanations
1414
// Here: https://lemire.me/blog/2022/12/19/implementing-strlen-using-sve/
1515
// Or: https://www.stonybrook.edu/commcms/ookami/support/_docs/5%20-%20Advanced%20SVE.pdf
16-
template<relative_conditional_expr C, logical_simd_value L>
17-
EVE_FORCEINLINE std::optional<std::ptrdiff_t>
18-
first_true_(EVE_SUPPORTS(sve_), C c, L m) noexcept
16+
template<callable_options O, typename T, typename N>
17+
EVE_FORCEINLINE std::optional<std::ptrdiff_t> first_true_(EVE_REQUIRES(sve_), O const& opts, logical<wide<T, N>> m) noexcept
18+
requires sve_abi<abi_t<T, N>>
1919
{
20+
using L = logical<wide<T, N>>;
21+
using C = rbr::result::fetch_t<condition_key, O>;
22+
auto c = opts[condition_key];
23+
2024
if constexpr( C::is_complete && !C::is_inverted ) return std::nullopt;
2125
else if constexpr( has_aggregated_abi_v<L> )
2226
{
2327
if constexpr( !C::is_complete ) m = m && sve_true(c, as(m));
2428
auto [lo, hi] = m.slice();
25-
auto lo_res = first_true[ignore_none](lo);
26-
auto hi_res = first_true[ignore_none](hi);
29+
auto lo_res = first_true(lo);
30+
auto hi_res = first_true(hi);
2731
if( lo_res ) return lo_res;
2832
if( hi_res ) *hi_res += lo.size();
2933
return hi_res;
3034
}
3135
else
3236
{
33-
auto c_m = L {sve_true(c, eve::as(m))};
37+
L c_m;
38+
39+
if constexpr (relative_conditional_expr<C>)
40+
{
41+
c_m = sve_true(c, eve::as(m));
42+
}
43+
else
44+
{
45+
c_m = expand_mask(c, as<L>{});
46+
}
3447

3548
// We don't need this much but it makes the `no matches` case
3649
// faster
@@ -40,23 +53,16 @@ EVE_FORCEINLINE std::optional<std::ptrdiff_t>
4053
if constexpr( !C::is_complete ) m = m && c_m;
4154

4255
eve::as_wide_t<eve::element_type_t<L>> first_true_mask =
43-
svbrkb_z(sve_true<element_type_t<L>>(), m);
56+
svbrkb_z(sve_true<element_type_t<L>>(), m);
4457
return count_true(first_true_mask);
4558
}
4659
}
4760

48-
template<logical_simd_value L>
49-
EVE_FORCEINLINE std::optional<std::ptrdiff_t>
50-
first_true_(EVE_SUPPORTS(sve_), L m) noexcept
51-
{
52-
return first_true[ignore_none](m);
53-
}
54-
55-
template<logical_simd_value L>
56-
EVE_FORCEINLINE std::optional<std::ptrdiff_t>
57-
first_true_(EVE_SUPPORTS(sve_), top_bits<L> m) noexcept
61+
template<callable_options O, typename T, typename N>
62+
EVE_FORCEINLINE std::optional<std::ptrdiff_t> first_true_(EVE_REQUIRES(sve_), O const& opts, top_bits<logical<wide<T, N>>> m) noexcept
63+
requires sve_abi<abi_t<T, N>>
5864
{
59-
return first_true(to_logical(m));
65+
return first_true.behavior(current_api, opts, to_logical(m));
6066
}
6167

6268
}

0 commit comments

Comments
 (0)