Conformance tests for oneapi::dpl::range algorithms & implementations - #2832
SergeyKopienko wants to merge 37 commits into
Conversation
42659d3 to
583ac5a
Compare
6d93df8 to
21422b7
Compare
SergeyKopienko
left a comment
There was a problem hiding this comment.
Per-macro notes on the new broken-test gates: for each one, what the library actually does wrong and why the existing tests did not catch it. Code references are permalinks into 0c93bdf55. No action is requested on this PR itself — the gates stay as they are; these notes are meant to make the follow-up library fixes easy to pick up.
| // std::input_iterator and std::output_iterator on the same pre-P2325R3 implementations. | ||
| #define _ONEDPL_CPP20_IN_OUT_ITERATOR_BROKEN TEST_STD_RANGES_VIEW_CONCEPT_REQUIRES_DEFAULT_INITIALIZABLE | ||
|
|
||
| #define _TEST_BROKEN_WRONG_RESULT_FIND_FIRST_OF_UNSEQ 1 |
There was a problem hiding this comment.
What is wrong: __simd_find_first_of violates [alg.find.first.of] in both branches. If the first range is shorter, unseq_backend_simd.h:805-816 captures __first by value before the loop, so ++__first never reaches the lambda's copy: every iteration compares the same *begin1, with the arguments swapped (__pred(*it2, *begin1)). The loop condition is invariant, so the brick can only answer position 0 or "not found" — any match past position 0 is reported as not found. Otherwise unseq_backend_simd.h:819-829 nests the loops the wrong way: the outer loop runs over the second range and returns on the first range-2 element that matches anywhere, not the minimum position in range 1 (n1=7, in1=10..16, in2[0]=16, in2[1]=13: correct 3, returned 6). Both unseq and par_unseq are affected, since the parallel pattern calls the same brick per chunk (algorithm_impl.h:892-904).
Why it did not show up earlier: the pre-existing data fills the first sequence with a single repeated value (in1(max_n1, [](size_t){ return T(1); })), so every match is at position 0 and the frozen *begin1 is representative; all three predicates used there are symmetric (equal_to, not_equal_to, x*x == y*y), which also hides the swapped argument order. The new test_match_away_from_the_front{,_long_first_range} give the first range distinct values and move the match off position 0, covering one branch each.
Possible fix: re-form the unary predicate per iteration with the arguments in range1/range2 order, and in the second branch scan range 1 in the outer loop (or take the minimum over range 2).
|
|
||
| #define _TEST_BROKEN_WRONG_RESULT_FIND_FIRST_OF_UNSEQ 1 | ||
|
|
||
| #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_COPY_IF_HETERO 1 |
There was a problem hiding this comment.
What is wrong: The device path assigns the input element through a const lvalue: the scan writer __write_to_id_if receives its payload tuple as const _ValueType& and passes std::get<2> on (parallel_backend_sycl_reduce_then_scan.h:164), and __pstl_assign binds the source to const _Xp& (utils.h:170-172); the single-work-group variant does the same at parallel_backend_sycl.h:298. ranges::copy_if only requires indirectly_copyable, i.e. *out = *in with iter_reference_t<In> a non-const In&, so an output type that declares only operator=(In&) is rejected.
Why it did not show up earlier: std_ranges_copy_if.pass.cpp instantiates only int, P2 and P3, whose implicit operator=(const T&) swallows the added const.
Possible fix: let __pstl_assign::operator() take the source as _Xp&& and forward it, and pass the payload tuple non-const.
| #define _TEST_BROKEN_WRONG_RESULT_FIND_FIRST_OF_UNSEQ 1 | ||
|
|
||
| #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_COPY_IF_HETERO 1 | ||
| #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_FIND_FIRST_OF 1 |
There was a problem hiding this comment.
What is wrong: host: indirect_binary_predicate<Pred, I1, I2> promises pred(*i1, *i2) only, never the reverse, yet in the shorter-first-range branch __simd_find_first_of calls __pred(element_of_range2, *__first) (unseq_backend_simd.h:805-812); the archetype has only the (lhs, rhs) overload, so the call fails. Device: first_match_pred copies the element into a const auto local and calls __pred(__elem, ...) (unseq_backend_sycl.h:628-637), needing a const-callable predicate, which is not promised either.
Why it did not show up earlier: existing tests compare ints with the generic binary_pred = [](auto&& v1, auto&& v2){ return v1 == v2; }, which takes either order and any const-ness.
Possible fix: pass the first-range element as the first argument; bind __elem by reference.
|
|
||
| #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_COPY_IF_HETERO 1 | ||
| #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_FIND_FIRST_OF 1 | ||
| #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_FIND_LAST_HETERO 1 |
There was a problem hiding this comment.
What is wrong: find_last forwards to find_last_if (glue_algorithm_ranges_impl.h:240), which reverses the input with std::ranges::reverse_view and calls find_if (glue_algorithm_ranges_impl.h:203-205). The device brick subscripts the range through a const lvalue (unseq_backend_sycl.h:122), but reverse_view has a const begin() only over a common_range, so a const reverse view of a non-common range has no operator[] — the clause requires only random_access_range + sized_range, never common_range.
Why it did not show up earlier: the pre-existing tests feed std::ranges::subrange<T*>, std::span<T> and std::vector<T>, all common ranges, so the const reverse_view keeps its operator[].
| #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_COPY_IF_HETERO 1 | ||
| #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_FIND_FIRST_OF 1 | ||
| #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_FIND_LAST_HETERO 1 | ||
| #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_FIND_LAST_IF_HETERO 1 |
There was a problem hiding this comment.
What is wrong: find_last_if builds a std::ranges::reverse_view over the input and passes it to find_if (glue_algorithm_ranges_impl.h:203-205); the device brick takes the range by const lvalue and subscripts it (unseq_backend_sycl.h:122). reverse_view exposes begin() const only when the underlying range is common_range, so the const reverse view is not random_access_range and view_interface gives it no operator[]; common_range is not part of the requires-clause. The host patterns walk the reversed view with iterators, hence the _HETERO suffix (the host build of the same file is clean).
Why it did not show up earlier: all existing inputs are pointer/vector based, i.e. common ranges.
Possible fix: reverse a common range, e.g. std::ranges::subrange(begin(r), begin(r) + size(r)).
| #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_SET_UNION 1 | ||
| #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_SORT 1 | ||
| #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_STABLE_PARTITION_HETERO 1 | ||
| #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_STABLE_SORT 1 |
There was a problem hiding this comment.
What is wrong: the same two causes as SORT, reached through __pattern_stable_sort. Host: the identical TBB split-merge binds its binary-search pivot as const _Ty& and therefore calls the comparator as (const T&, T&)/(T&, const T&) (parallel_backend_tbb.h:1022-1038); under the OpenMP backend the sized std::vector<_ValueType> merge buffer also demands default-constructibility (omp/parallel_stable_sort.h:88-107). Device: the projected-key call selects the one-work-group radix sort, which placement-news into its storage and hence needs operator& (radix_sort_one_wg.h:123, :311).
Why it did not show up earlier: std::sortable promises neither default-constructibility nor addressability, but every value type sorted so far (int, P2) has both.
| #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_SORT 1 | ||
| #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_STABLE_PARTITION_HETERO 1 | ||
| #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_STABLE_SORT 1 | ||
| #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_UNIQUE_COPY_HETERO 1 |
There was a problem hiding this comment.
What is wrong: Two const-adding sites. The unique pattern copies element 0 eagerly via __write_op.__assign(__in_rng[0], __out_rng[0]) (parallel_backend_sycl_reduce_then_scan.h:2158), and both that call and the per-element writer go through __pstl_assign, which binds the source to const _Xp& (utils.h:170-172). ranges::unique_copy only requires indirectly_copyable, i.e. assignment from a non-const In&.
Why it did not show up earlier: std_ranges_unique_copy.pass.cpp uses only int and P2 with identical in/out types, whose implicit operator=(const T&) hides the const.
Possible fix: forward the source in __pstl_assign instead of const-qualifying it.
| #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_STABLE_PARTITION_HETERO 1 | ||
| #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_STABLE_SORT 1 | ||
| #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_UNIQUE_COPY_HETERO 1 | ||
| #define _TEST_CPP20_RANGES_BROKEN_WRONG_RESULT_FIND_FIRST_OF_PROJ1_HOST 1 |
There was a problem hiding this comment.
What is wrong: unseq/par_unseq reach __simd_find_first_of; seq is unaffected (std::ranges::find_first_of). When the first range is the shorter one, unseq_backend_simd.h:805-816 forms the predicate as __pred(second_range_element, *__first), so proj1 lands on the second range, and __first is captured by value before the loop, freezing it at begin. For r1={0,1,2,…}, r2={4i+2}, pred = == and proj1 = ×2 the answer must be index 1, but the brick only ever tests 2*(4j+2) == 0 and returns end (the 1012-vs-2025 shape). The other branch, unseq_backend_simd.h:819-829, keeps the argument order but nests the loops the wrong way round. Same root cause as _TEST_BROKEN_WRONG_RESULT_FIND_FIRST_OF_UNSEQ above.
Why it did not show up earlier: the earlier cases use data whose match sits at index 0 (both ranges generated identically, or a negated needle with no match at all), so the frozen *begin1 is representative and the misdirected projection cancels; the new case is the first with a 4i+2 needle plus a ×2 projection, i.e. a match away from the front.
Possible fix: apply the projections through the same wrapper orientation as the non-vector brick and re-read *__first inside the loop.
| #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_STABLE_SORT 1 | ||
| #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_UNIQUE_COPY_HETERO 1 | ||
| #define _TEST_CPP20_RANGES_BROKEN_WRONG_RESULT_FIND_FIRST_OF_PROJ1_HOST 1 | ||
| #define _TEST_CPP20_RANGES_BROKEN_WRONG_RESULT_LEXICOGRAPHICAL_COMPARE_PROJ1_HOST 1 |
There was a problem hiding this comment.
What is wrong: the vectorized host brick invokes the two-projection wrapper with reversed arguments: std::invoke(__comp, __y, __x) as the second half of the equivalence test (algorithm_impl.h:5104-5106) and again in the shorter-sequence tie-break (algorithm_impl.h:5108-5111). __simd_first hands the lambda (range1, range2), so the reversed call applies proj1 to a second-range element, while [alg.lex.comparison] prescribes comp(proj2(*first2), proj1(*first1)). With r1={5,1,0…}, r2={4,9,0…}, less and proj1 = ×2, index 0 is judged equivalent — both halves are false, less(2*5,4) and less(2*4,5), where the correctly oriented second half less(4, 2*5) is true — so the scan runs on to index 1 and the call returns true: expected false, got true, 2025 elements. Only unseq is affected: seq uses std::ranges::lexicographical_compare, and the parallel pattern is correct because it wraps the comparator in __reorder_pred, whose __binary_op specialization (utils.h:208-216) reverses only the user comparator and keeps each projection on its own sequence.
Why it did not show up earlier: in all earlier cases the first differing pair already decides the result under either argument order; the new generators hide a pair (5 vs 4) that only the correctly-oriented call sees as different.
Possible fix: use __reorder_pred<_Compare> for both reversed invocations in the vectorized brick, as the parallel pattern does.
| #define _TEST_CPP20_RANGES_BROKEN_REQUIRES_UNIQUE_COPY_HETERO 1 | ||
| #define _TEST_CPP20_RANGES_BROKEN_WRONG_RESULT_FIND_FIRST_OF_PROJ1_HOST 1 | ||
| #define _TEST_CPP20_RANGES_BROKEN_WRONG_RESULT_LEXICOGRAPHICAL_COMPARE_PROJ1_HOST 1 | ||
| #define _TEST_CPP20_RANGES_BROKEN_WRONG_RESULT_PARTIAL_SORT_COPY_PROJ1 1 |
There was a problem hiding this comment.
What is wrong: the parallel and the device ranges patterns both drop proj1: the parameter is left unnamed and the comparator is wrapped as __binary_op<_Comp, _Proj2, _Proj2> — algorithm_ranges_impl.h:499-516 and hetero/algorithm_ranges_impl_hetero.h:1527-1539. Selection therefore projects input elements with proj2, while the standard defines it via comp(proj1(*in), proj2(*out)). seq/unseq take algorithm_ranges_impl.h:518-527 and do honour proj1, so for a pair of projections that order elements differently (-v.x vs v.x) the results cannot agree: par policy disagrees with seq policy, at index 8184. For such an order-inconsistent pair the exact answer is not portable, which is why only cross-policy consistency is checked.
Why it did not show up earlier: every other call passes proj1 == proj2 or a pair inducing the same order (&P2::proj vs &P2::x), where ignoring proj1 is unobservable.
Possible fix: keep proj2 for sorting the output, but select with __binary_op<_Comp, _Proj1, _Proj2> for the input-vs-output comparison.
a1b9843 to
b75a636
Compare
… type in rotate_copy_tester
…the dangling iterators check The check of the dangling iterators reused the policy name of the real call, so a tester returning `auto` made that check instantiate device code with an already used SYCL kernel name. Give the real call the index 0 and the check the index 1.
…the right sequences The existing cases hand one and the same projection to both sequences, so an algorithm which mixes its two projections up is indistinguishable from a correct one. Check it on two levels: with the unrelated value types A and B and projections accepting only their own type, where a mix-up is a compile-time error, and with projections which differ in value, where it is a wrong result. lexicographical_compare fails both with the unseq policy; those two cases are closed by the macros already in test_config.h.
…k the input projection The output elements are copies of the input ones, so applying the wrong projection to the input sequence stays invisible as long as both projections order the elements the same way, and an order inconsistent pair makes the selected elements implementation defined. Compare the policies with each other instead of with std::ranges: the parallel host pattern and the device pattern drop _Proj1 and select other elements than seq does.
…pe and call id uninitialized_copy and uninitialized_move may write a type other than the one they read, and a translation unit running more than one device call needs a unique SYCL kernel name per call. Add the OutElem and call_id template parameters; both are defaulted, so the existing tests are unaffected.
… pin the answer All the elements of the first range are equal in the existing data and all the predicates are symmetric, so an implementation which always returns the first element, or which calls the predicate with the arguments swapped, passes every case. Add ranges of distinct values with two matches planted in the reversed order and a non-symmetric predicate, for both orders of the two lengths. The position is compared, not the iterator, because EXPECT_EQ cannot print an iterator.
…lace merge The cases of the inplace merge are closed by two macros: one of them covers the host calls of two cases, the other one covers a case entirely. Both stand for a requirement the implementation does not meet, and the test has no use for the difference between them: the host macro is dropped, all the cases are closed by the common one, and the ones which were split for the sake of the dropped macro go back to a single call over all the policies.
…ult constructible elements The OpenMP backend of the parallel stable sort keeps its merge buffer in a std::vector, which requires the value type of the range to be default constructible, so no host call of sort, of stable_sort or of partial_sort over an archetype without a default constructor compiles with that backend. Every such case is closed by a macro. Since the defect covers all the cases of the sort and of the stable sort, the host macro and the device macro of each of them are merged into a single one: telling the sides apart buys nothing any more, and the cases which were split for the sake of the pair go back to a single call over all the policies. The cases of the partial sort are split into the two sides instead, because its macro covers the host alone.
…a gated call The cases of the archetype suite which are gated for one side only keep the call and the checker in local variables shared by the two sides. Both variables are left unreferenced once the gated side is the only user of them and the device policies are not compiled in, which the -Werror of the public CI turns into a build failure. Such a case is compiled out as a whole now. The constant of the searched value is moved out of the function for the same reason. The ungated cases use it as well, so the case of a gated call cannot cover it, and it is read inside lambdas only, which gcc does not count as a use of a local variable.
The two cases the algorithm cannot pass yet were left ungated when they were added. The mixed type host case needs the already known FIND_FIRST_OF macro, the projected one gets a new macro of its own: the host implementation applies the projection of the first range to the elements of the second one.
The device path builds the reverse functor from a braced initializer, so the size of the range has to convert to the index type of the functor without a narrowing conversion. It does not when the size type of the range is signed, which is the case with libstdc++ and the view of the archetype storage.
The simd path of find_first_of compares every element of the first range with the first element of the second one only when the first range is the shorter of the two, and stops at the first element of the first range otherwise, so it finds a match away from the front of the range at the wrong position or not at all. The two cases that show it are closed by a new macro.
…TERO and _TEST_CPP20_RANGES_BROKEN_REQUIRES_LEXICOGRAPHICAL_COMPARE_HOST together
…ind_first_of checks _TEST_BROKEN_WRONG_RESULT_FIND_FIRST_OF_UNSEQ compiled out both new position cases as a whole, but each of them runs through invoke_on_all_policies, so seq, par, the device policies and the non random access iterator variants lost the new coverage as well. The decision is moved into the test functors, where the library's own backend selection tells whether the call ends up in the vectorized brick, and only such calls are skipped.
b75a636 to
86a3a88
Compare
…ype -> _DifferenceType
… in __simd_find_first_of() The __n1 < __n2 branch created the functor passed to __simd_or() once, before the loop over the first sequence, capturing __first by value. The capture kept pointing at the initial position for the whole loop, so every iteration compared the elements of the second sequence against the *first* element of the first sequence again. The brick could therefore only return two values: __first, if any element of the second sequence matches *__first, and __last otherwise - a match at any other position was never reported. Example (unseq, equality): find_first_of([1, 2], [9, 8, 7, 2]) returned index 2 (== __last) instead of index 1. The functor is now created inside the loop, directly at the __simd_or() call, so it holds the element of the current iteration; being created per iteration it also no longer needs to be mutable. The defect appeared together with the lambda in 954ce1a - before that commit the functor (__internal::__equal_value_by_pred) was constructed inside the loop.
…simd_find_first_of() [alg.find.first.of] specifies the result as the first iterator i of the first sequence for which pred(*i, *j) is true for some iterator j of the second sequence, so the element of the first sequence has to be passed as the first argument. The __n1 < __n2 branch called the predicate the other way round, pred(*j, *i). With a symmetric predicate - the common case, e.g. equality - this is invisible, which is why it went unnoticed. Any asymmetric predicate gives a wrong result, and the result also depends on the sizes of the sequences, because the __n1 >= __n2 branch of the same brick passes the arguments in the correct order. Example (unseq, pred = std::less): find_first_of([3, 9], [4, 4, 4, 4]) returned index 2 (== __last) instead of index 0, because 4 < 3 is false while 3 < 4 is true. The swapped order comes from the original PSTL code (821780f): that branch used __internal::__equal_value_by_pred, whose operator() already invoked _M_pred(__arg, _M_value), and the lambda introduced in 954ce1a kept the order.
…_of() The __n1 >= __n2 branch scans the whole first sequence once per element of the second sequence and returned the first match it found. That is the leftmost match of *some* element of the second sequence - in fact of the earliest one which matches at all - while the standard asks for the earliest position in the *first* sequence over all the elements of the second one. Example (unseq, equality): find_first_of([5, 3, 7, 9], [3, 5]) returned index 1, the match of s[0] == 3, instead of index 0, where a[0] == 5 matches s[1]. The loop now keeps the best position found so far and passes it to __simd_first() as the upper bound of the next search, so a later scan can only improve the result. Bounding the search this way is also cheaper than the original code even in the worst case: the scanned prefix shrinks monotonically instead of covering the whole first sequence __n2 times, and the loop stops as soon as position 0 is reached. When nothing is found the bound stays at __n1 and __last is returned. The defect is as old as the initial PSTL snapshot 821780f.
… additional test cases
…o/range_conformance_tests # Conflicts: # test/parallel_api/algorithm/alg.nonmodifying/find_first_of.pass.cpp
Co-authored-by: Dmitriy Sobolev <Dmitriy.Sobolev@intel.com>
…nd_first_of() & introduce local variables for internal lambdas
…ew cases, consider updating the existing ones. Having a match not in the beginning is a basic scenario - it should be covered in the core tests - remove extra comments
Co-authored-by: Dmitriy Sobolev <Dmitriy.Sobolev@intel.com>
…o/range_conformance_tests
…T as not needed anymore - it has been fixed in 4b6a51c
… anymore - it has been fixed in 4b6a51c + introduce _TEST_CPP20_RANGES_BROKEN_REQUIRES_FIND_FIRST_OF_HETERO for the rest of broken test cases with hetero policy
6e5ed71 to
0809f81
Compare
…l_compare
The vectorized brick expressed the reversed comparison by swapping its own
arguments, __comp(__y, __x), in two places: in the __simd_first predicate that
searches for the first non-equivalent pair, and in the tie-break taken when the
first sequence turns out to be the shorter one.
For the iterator-level API that is correct, because there __comp is a plain
comparator over the two value types. For the range-based API it is not:
oneapi::dpl::ranges::lexicographical_compare passes
__internal::__binary_op<_Comp, _Proj1, _Proj2> down to
__pattern_lexicographical_compare, and
__binary_op{}(__a, __b) == _Comp(_Proj1(__a), _Proj2(__b)),
that is, the projection is selected by the argument position and not by the
sequence the element comes from. Swapping the arguments therefore swapped the
sequences as well: _Proj1 was applied to the second range and _Proj2 to the
first one.
__internal::__reorder_pred is exactly the tool for this. Its specialization for
__binary_op (utils.h) reverses the operands of the comparator only and keeps
each projection on its own sequence, so it is used here now - the same way as
the host-parallel overload of __pattern_lexicographical_compare and the device
implementation (__pattern_lexicographical_compare_transform_fn) already do. The
vectorized brick was the last site with the swapped arguments.
An example of the wrong result, with comp = std::ranges::less,
proj1 = [](int __v) { return 2 * __v; } and proj2 = std::identity{}:
r1 = {5, 1, 0, 0, ...}
r2 = {4, 9, 0, 0, ...}
Position 0 is the first mismatch and 2 * 5 == 10 is not less than 4, so the
expected result is false. With the swapped arguments position 0 looked
equivalent (neither 2 * 5 < 4 nor 2 * 4 < 5 holds), the search went on to
position 1 and returned 2 * 1 < 9, i.e. true.
The tie-break was wrong in the same way: for sequences that are equal
element-wise after the projections and |r1| < |r2| it evaluated
_Comp(_Proj1(r2[i]), _Proj2(r1[i])) instead of
_Comp(_Proj2(r2[i]), _Proj1(r1[i])), which yields the wrong answer as soon as
the two projections differ.
Only the unseq policies were affected. Verified with icpx, -std=c++20, on the
host (serial and TBB backends) and with -fsycl.
None of the existing cases can detect a swap of the two projections:
* cases 0, 5 and 6 pass no projection at all;
* cases 2 and 3 use &P2::x and &P2::proj, and P2::proj() returns x, so the two
projections are indistinguishable;
* case 1 has plus_one against the identity, but both pairings already differ at
position 0, so the answer is taken from the very first pair and is the same
either way;
* case 4 generates the second sequence with plus_one as well, so the projected
sequences are equal under both pairings and the swap cancels out.
Two cases are added, both with a single non-trivial projection of the first
sequence (proj = 2 * value) and with per-sequence data generators:
* case 7 hides the real first mismatch behind a pair that looks equivalent only
when the projections are swapped - r1 = {5, 1, 0, ...}, r2 = {4, 9, 0, ...}:
the expected answer is false (2 * 5 is not less than 4 at position 0), while a
swapping implementation finds position 0 equivalent and answers true at
position 1;
* case 8 makes the projected sequences equal element-wise (r1 = {-1, -1, ...},
r2 = {-2, -2, ...}), so the result is decided by the sizes alone; this covers
the tie-break branch for the case when the first sequence is the shorter one,
which the data_in_in shape (n / 2, n) exercises.
Both cases fail on unseq without the fix of __brick_lexicographical_compare
(icpx, -std=c++20, host serial and TBB backends, and -fsycl):
case 7: wrong return value ... for 2025 and 2025 elements, expected false, got true
case 8: wrong return value ... for 1012 and 2025 elements, expected true, got false
The failure of case 8 appears once the __simd_first predicate alone is repaired;
the test exits on the first failed expectation, so the necessity of the two
changed lines of the brick was checked with separate per-line mutants.
…ev/skopienko/range_conformance_tests # Conflicts: # test/parallel_api/ranges/std_ranges_lexicographical_compare.pass.cpp
…RAPHICAL_COMPARE_PROJ1_HOST as not required anymore
Summary
Adds conformance tests for the
oneapi::dpl::rangesalgorithms: tests that check the algorithms against the minimal requirements the standard puts on element types, callables and ranges, instead of against convenient types likeint.The tests instantiate every algorithm with archetype element types that provide exactly what the corresponding concept demands and nothing more (no default construction, no copy, no
operator&, no comparison operators, unrelated input/output types, etc.), plus predicates/comparators/projections that are only callable on non-const references. Anything the implementation requires beyond the standard therefore fails to compile or to link, rather than silently passing.What is added
New directory
test/parallel_api/ranges/conformance/:std_ranges_archetypes*.h— archetype element types, callables and views:archetype_iterator/archetype_sentinel/archetype_view: random-access, sized, borrowed, non-contiguous, non-common range built onview_interface;plain_archetype_view: the same range without theview_interfacemembers (size(),operator[],empty(),front()), to catch code that relies on them;min/max/minmax), memory;_dcvariants that are trivially/device copyable for the SYCL backend, withstatic_asserts onsycl::is_device_copyable_v;static_asserts pinning down what each archetype is not (not default-initializable, not copyable, not totally ordered, ...), so the archetypes cannot silently become too permissive.std_ranges_algo_archetypes_test.h— driversrun_algo*/run_algo2*that run a call overseq,unseq,par,par_unseqand, when available, a device policy, allocating storage withstd::allocatororsycl::usm_allocatoraccordingly...._read,..._value,..._write,..._permute,..._merge,..._cross,..._storable, andstd_ranges_memory_archetypes.pass.cpp.Each family is covered three times where applicable: with const callables, with callables taking their arguments by non-const reference, and with the default predicate/comparator.
Existing tests
std_ranges_test.h: helperscheck_mixed_types_in_in_{host,device},check_mixed_types_in_in_out_{host,device}and theresult_*reducers; the two-range tests (equal,mismatch,search,find_end,find_first_of,starts_with,ends_with,contains_subrange,lexicographical_compare,merge,transform) now verify that each projection is applied to its own sequence, both by value and by type. SeveralCLONE_TEST_POLICYcalls got explicit indices to avoid kernel name collisions.std_ranges_memory_test.h: input and output elements may now be different types.std_ranges_partial_sort_copy.pass.cpp: added a cross-policy consistency check for the two projections.find_first_of.pass.cpp: added cases with the match away from the front of the first sequence.Known implementation gaps
Calls that currently fail to compile or return a wrong result are disabled through
_TEST_CPP20_RANGES_BROKEN_*macros intest/support/test_config.h, each naming the algorithm and the affected policy group (host/hetero). The list is intended as the work item list for the follow-up fixes; every macro should eventually be removed together with the corresponding implementation fix.