From 153a69ea375d8b5adeb98357a6c485eacaa074fd Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Wed, 22 Jul 2026 08:19:12 -0700 Subject: [PATCH 1/2] Add test coverage. --- .../VSO_0000000_vector_algorithms/test.cpp | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/tests/std/tests/VSO_0000000_vector_algorithms/test.cpp b/tests/std/tests/VSO_0000000_vector_algorithms/test.cpp index b46bc7babf6..acd85fbfaa4 100644 --- a/tests/std/tests/VSO_0000000_vector_algorithms/test.cpp +++ b/tests/std/tests/VSO_0000000_vector_algorithms/test.cpp @@ -620,6 +620,37 @@ void test_min_max_element_special_cases() { == v.begin() + 2 * block_size_in_elements + last_vector_first_elem + 9); } +// GH-6373 ARM64/ARM64EC vectorized minmax_element() family mishandles unsigned elements +void test_gh_6373() { + // These test cases are 16 bytes, to reach the vectorization threshold. + + // In these cases, the minimum is 2^(N-1) - 1 and is located after index 0. + test_case_min_max_element( + vector{155, 153, 248, 150, 189, 140, 247, 178, 244, 164, 226, 214, 239, 215, 176, 127}); + test_case_min_max_element(vector{0x8011, 0x8022, 0x8033, 0x8044, 0x8055, 0x8066, 0x8077, 0x7FFF}); + test_case_min_max_element(vector{0x8000'0011UL, 0x8000'0022UL, 0x8000'0033UL, 0x7FFF'FFFFUL}); + test_case_min_max_element(vector{0x8000'0000'0000'0011ULL, 0x7FFF'FFFF'FFFF'FFFFULL}); + + // In these cases, the maximum is 2^(N-1) and is located after index 0. + test_case_min_max_element(vector{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 128}); + test_case_min_max_element(vector{1, 2, 3, 4, 5, 6, 7, 0x8000}); + test_case_min_max_element(vector{1, 2, 3, 0x8000'0000UL}); + test_case_min_max_element(vector{1, 0x8000'0000'0000'0000ULL}); + + { + // This is an extra test case to exercise an additional failure mode that was resolved by the same fix. + // For uint8_t, _Max_portion_byte_size = _Portion_max * _Vec_size = 256 * 16 = 4096. + // This test case has 4112 = 4096 + 16 bytes, which is a max portion followed by a single vector. + // The scenario is when all of the first 4096 bytes are >= 128, then any of the last 16 bytes are < 128. + vector v(4112, 200); + v[1729] = 222; + v[3000] = 222; + v[4100] = 11; + v[4105] = 11; + test_case_min_max_element(v); + } +} + template void test_is_sorted_until(mt19937_64& gen) { using Limits = numeric_limits; @@ -1325,6 +1356,8 @@ void test_vector_algorithms(mt19937_64& gen) { test_case_min_max_element( vector{-6604286336755016904, -4365366089374418225, 6104371530830675888, -8582621853879131834}); + test_gh_6373(); + test_is_sorted_until(gen); test_is_sorted_until(gen); test_is_sorted_until(gen); From dfab9fb127e237c4f210ef38aea3b04773ca7906 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Wed, 22 Jul 2026 08:19:52 -0700 Subject: [PATCH 2/2] Fix bug. (By Opus 4.8, cleaned up by STL.) --- stl/src/vector_algorithms.cpp | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/stl/src/vector_algorithms.cpp b/stl/src/vector_algorithms.cpp index 25dbc33e8d4..8696dcb78be 100644 --- a/stl/src/vector_algorithms.cpp +++ b/stl/src/vector_algorithms.cpp @@ -2970,6 +2970,13 @@ namespace { auto _Cur_min_val = _Traits::_Init_min_val; auto _Cur_max_val = _Traits::_Init_max_val; +#if defined(_M_ARM64) || defined(_M_ARM64EC) + if constexpr (!_Sign && _Traits::_Has_unsigned_cmp) { + _Cur_min_val = -1; + _Cur_max_val = 0; + } +#endif // ^^^ defined(_M_ARM64) || defined(_M_ARM64EC) ^^^ + if constexpr (_Traits::_Vectorized) { auto _Base = static_cast(_First); size_t _Portion_byte_size = _Byte_length(_First, _Last) & ~_Traits::_Vec_mask; @@ -3029,6 +3036,14 @@ namespace { return _Traits::_H_max_u(_Vals); } }; + const auto _Less_wrap = [](const auto _Lhs, const auto _Rhs) noexcept { + if constexpr (_Sign || !_Traits::_Has_unsigned_cmp) { + return _Lhs < _Rhs; + } else { + using _UTy = _Traits::_Unsigned_t; + return static_cast<_UTy>(_Lhs) < static_cast<_UTy>(_Rhs); + } + }; #else // ^^^ defined(_M_ARM64) || defined(_M_ARM64EC) / !defined(_M_ARM64) && !defined(_M_ARM64EC) vvv const auto _Cmp_gt_wrap = [](const auto _First, const auto _Second) noexcept { return _Traits::_Cmp_gt(_First, _Second); @@ -3041,6 +3056,7 @@ namespace { }; const auto _H_min_wrap = [](const auto _Vals) noexcept { return _Traits::_H_min(_Vals); }; const auto _H_max_wrap = [](const auto _Vals) noexcept { return _Traits::_H_max(_Vals); }; + const auto _Less_wrap = [](const auto _Lhs, const auto _Rhs) noexcept { return _Lhs < _Rhs; }; #endif // ^^^ !defined(_M_ARM64) && !defined(_M_ARM64EC) ^^^ const auto _Update_min_max = [&](const auto _Cur_vals, [[maybe_unused]] const auto _Blend_idx_0, @@ -3134,7 +3150,7 @@ namespace { const auto _H_min = _H_min_wrap(_Cur_vals_min); const auto _H_min_val = _Traits::_Get_any(_H_min); // Get any element of it - if (_H_min_val < _Cur_min_val) { // Current horizontal min is less than the old + if (_Less_wrap(_H_min_val, _Cur_min_val)) { // Current horizontal min is less than the old _Cur_min_val = _H_min_val; // update min // Mask of all elems eq to min const auto _Eq_mask = _Traits::_Cmp_eq(_H_min, _Cur_vals_min); @@ -3161,8 +3177,8 @@ namespace { const auto _H_max = _H_max_wrap(_Cur_vals_max); const auto _H_max_val = _Traits::_Get_any(_H_max); // Get any element of it - if (_Mode == _Mode_both && _Cur_max_val <= _H_max_val - || _Mode == _Mode_max && _Cur_max_val < _H_max_val) { + if (_Mode == _Mode_both && !_Less_wrap(_H_max_val, _Cur_max_val) + || _Mode == _Mode_max && _Less_wrap(_Cur_max_val, _H_max_val)) { // max_element: current horizontal max is greater than the old, update max // minmax_element: current horizontal max is not less than the old, update max _Cur_max_val = _H_max_val;