From b379eb38285dbf7ca455a3f32e6c13c11c0e8418 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20M=C3=BCller?= Date: Mon, 2 Feb 2026 16:07:38 +0100 Subject: [PATCH 01/11] ``: Fix reentrant loops containing backreferences (#6055) --- stl/inc/regex | 33 +++++++++++-------- .../std/tests/VSO_0000000_regex_use/test.cpp | 2 ++ 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/stl/inc/regex b/stl/inc/regex index 35ba5ef909b..02c3c2d85c5 100644 --- a/stl/inc/regex +++ b/stl/inc/regex @@ -1269,18 +1269,18 @@ _INLINE_VAR constexpr unsigned int _Bmp_size = (_Bmp_max + _Bmp_chrs - 1U) / _B _INLINE_VAR constexpr unsigned int _ARRAY_THRESHOLD = 4U; enum _Node_flags : int { // flags for nfa nodes with special properties - _Fl_none = 0x000, - _Fl_negate = 0x001, - _Fl_greedy = 0x002, - _Fl_longest = 0x008, // TRANSITION, ABI: 0x004 is unused; the parser previously marked some nodes with it - _Fl_class_negated_w = 0x100, - _Fl_class_negated_s = 0x200, - _Fl_class_negated_d = 0x400, - _Fl_class_cl_all_bits = 0x800, // TRANSITION, ABI: GH-5242 - _Fl_begin_needs_w = 0x100, - _Fl_begin_needs_s = 0x200, - _Fl_begin_needs_d = 0x400, - _Fl_rep_branchless = 0x800, + _Fl_none = 0x0000, + _Fl_negate = 0x0001, + _Fl_greedy = 0x0002, + _Fl_longest = 0x0008, // TRANSITION, ABI: 0x004 is unused; the parser previously marked some nodes with it + _Fl_class_negated_w = 0x0100, + _Fl_class_negated_s = 0x0200, + _Fl_class_negated_d = 0x0400, + _Fl_class_cl_all_bits = 0x0800, // TRANSITION, ABI: GH-5242 + _Fl_begin_needs_w = 0x0100, + _Fl_begin_needs_s = 0x0200, + _Fl_begin_needs_d = 0x0400, + _Fl_rep_branchless = 0x1000, }; _BITMASK_OPS(_EMPTY_ARGUMENT, _Node_flags) @@ -5511,6 +5511,14 @@ void _Parser2<_FwdIt, _Elem, _RxTraits>::_Calculate_loop_simplicity( } break; + case _N_back: + if (_Outer_rep && !_Nonreentrant) { + // The content and thus length of a back-reference may change + // when a loop is reentered + _Outer_rep->_Flags &= ~_Fl_rep_branchless; + } + break; + case _N_group: case _N_capture: case _N_none: @@ -5523,7 +5531,6 @@ void _Parser2<_FwdIt, _Elem, _RxTraits>::_Calculate_loop_simplicity( case _N_end_group: case _N_end_assert: case _N_end_capture: - case _N_back: case _N_endif: case _N_begin: case _N_end: diff --git a/tests/std/tests/VSO_0000000_regex_use/test.cpp b/tests/std/tests/VSO_0000000_regex_use/test.cpp index e33d1d2165b..f50db71aefc 100644 --- a/tests/std/tests/VSO_0000000_regex_use/test.cpp +++ b/tests/std/tests/VSO_0000000_regex_use/test.cpp @@ -2437,6 +2437,8 @@ void test_gh_6022() { g_regexTester.should_not_match( "bacabcdacdabbaddabbcdcdba", R"((?:(?:([abc])([abc]))*d)*cabcdacdabbaddabbcdcd\1\2)"); g_regexTester.should_match("bacabcdacdabbaddabbcdcd", R"((?:(?:([abc])([abc]))*d)*bacabcdacdabbaddabbcdcd\1\2)"); + + g_regexTester.should_match("abaaacaabaaaadab", R"((?:(a*)b(\1*)a*c)+aabaaaad\2b)"); } int main() { From 353b2b9942e3201b686805688b8ed49c5e91d50d Mon Sep 17 00:00:00 2001 From: Hari Limaye Date: Mon, 2 Feb 2026 11:09:22 -0400 Subject: [PATCH 02/11] Add Neon implementation of `find_last` (#6030) Co-authored-by: Stephan T. Lavavej --- stl/inc/xutility | 2 +- stl/src/vector_algorithms.cpp | 170 +++++++++++++++++++++++++++------- 2 files changed, 138 insertions(+), 34 deletions(-) diff --git a/stl/inc/xutility b/stl/inc/xutility index d655605337c..8f5ef8e9207 100644 --- a/stl/inc/xutility +++ b/stl/inc/xutility @@ -85,7 +85,7 @@ _STL_DISABLE_CLANG_WARNINGS #define _VECTORIZED_FIND _VECTORIZED_FOR_X64_X86_ARM64 #define _VECTORIZED_FIND_END _VECTORIZED_FOR_X64_X86 #define _VECTORIZED_FIND_FIRST_OF _VECTORIZED_FOR_X64_X86 -#define _VECTORIZED_FIND_LAST _VECTORIZED_FOR_X64_X86 +#define _VECTORIZED_FIND_LAST _VECTORIZED_FOR_X64_X86_ARM64 #define _VECTORIZED_FIND_LAST_OF _VECTORIZED_FOR_X64_X86 #define _VECTORIZED_INCLUDES _VECTORIZED_FOR_X64_X86 #define _VECTORIZED_IS_SORTED_UNTIL _VECTORIZED_FOR_X64_X86_ARM64 diff --git a/stl/src/vector_algorithms.cpp b/stl/src/vector_algorithms.cpp index 57f33bfc920..c1c2876306c 100644 --- a/stl/src/vector_algorithms.cpp +++ b/stl/src/vector_algorithms.cpp @@ -4170,6 +4170,14 @@ namespace { unsigned long _Get_first_h_pos_d(const uint64_t _Mask) noexcept { return _CountTrailingZeros64(_Mask) >> 3; } + + unsigned long _Get_last_h_pos_q(const uint64_t _Mask) noexcept { + return 15 - (_CountLeadingZeros64(_Mask) >> 2); + } + + unsigned long _Get_last_h_pos_d(const uint64_t _Mask) noexcept { + return 7 - (_CountLeadingZeros64(_Mask) >> 3); + } #elif defined(_M_ARM64EC) using _Find_traits_1 = void; using _Find_traits_2 = void; @@ -4278,6 +4286,27 @@ namespace { return _Ptr; } + template <_Predicate _Pred, class _Ty> + const void* _Find_last_scalar_tail( + const void* const _First, const void* const _Last, const void* const _Real_last, const _Ty _Val) noexcept { + auto _Ptr = static_cast(_Last); + + while (_Ptr != _First) { + --_Ptr; + if constexpr (_Pred == _Predicate::_Not_equal) { + if (*_Ptr != _Val) { + return _Ptr; + } + } else { + if (*_Ptr == _Val) { + return _Ptr; + } + } + } + + return _Real_last; + } + // The below functions have exactly the same signature as the extern "C" functions, up to calling convention. // This makes sure the template specialization can be fused with the extern "C" function. // In optimized builds it avoids an extra call, as these functions are too large to inline. @@ -4333,7 +4362,7 @@ namespace { } while (_First != _Stop_at); } - if ((_Size_bytes & size_t{0x10}) != 0) { + if ((_Size_bytes & size_t{0x10}) != 0) { // use original _Size_bytes; we've read only 32-byte chunks const auto _Comparand = _Traits::_Set_neon_q(_Val); const auto _Data = _Traits::_Load_q(_First); @@ -4354,7 +4383,7 @@ namespace { } if constexpr (sizeof(_Ty) < 8) { - if ((_Size_bytes & size_t{0x08}) != 0) { + if ((_Size_bytes & size_t{0x08}) != 0) { // use original _Size_bytes; we've read only 16/32-byte chunks const auto _Comparand = _Traits::_Set_neon(_Val); const auto _Data = _Traits::_Load(_First); @@ -4377,6 +4406,99 @@ namespace { return _Find_scalar_tail<_Pred>(_First, _Last, _Val); } + + template + const void* __stdcall _Find_last_impl(const void* const _First, const void* _Last, const _Ty _Val) noexcept { + const void* const _Real_last = _Last; + const size_t _Size_bytes = _Byte_length(_First, _Last); + + if (const size_t _Neon_size = _Size_bytes & ~size_t{0x1F}; _Neon_size != 0) { + const auto _Comparand = _Traits::_Set_neon_q(_Val); + const void* _Stop_at = _Last; + _Rewind_bytes(_Stop_at, _Neon_size); + + do { + _Rewind_bytes(_Last, 32); + const auto _Data_lo = _Traits::_Load_q(static_cast(_Last) + 0); + const auto _Data_hi = _Traits::_Load_q(static_cast(_Last) + 16); + + const auto _Comparison_lo = _Traits::_Cmp_neon_q(_Data_lo, _Comparand); + const auto _Comparison_hi = _Traits::_Cmp_neon_q(_Data_hi, _Comparand); + + // Use a fast check for the termination condition. + uint64_t _Any_match = 0; + if constexpr (_Pred == _Predicate::_Not_equal) { + _Any_match = _Traits::_Match_mask_ne(_Comparison_lo, _Comparison_hi); + } else { + _Any_match = _Traits::_Match_mask_eq(_Comparison_lo, _Comparison_hi); + } + + if (_Any_match != 0) { + auto _Mask_hi = _Traits::_Mask_q(_Comparison_hi); + if constexpr (_Pred == _Predicate::_Not_equal) { + _Mask_hi ^= 0xFFFF'FFFF'FFFF'FFFF; + } + + if (_Mask_hi != 0) { + const auto _Offset = _Get_last_h_pos_q(_Mask_hi) + 16; + _Advance_bytes(_Last, _Offset - (sizeof(_Ty) - 1)); + return _Last; + } + + auto _Mask_lo = _Traits::_Mask_q(_Comparison_lo); + if constexpr (_Pred == _Predicate::_Not_equal) { + _Mask_lo ^= 0xFFFF'FFFF'FFFF'FFFF; + } + + const auto _Offset = _Get_last_h_pos_q(_Mask_lo); + _Advance_bytes(_Last, _Offset - (sizeof(_Ty) - 1)); + return _Last; + } + } while (_Last != _Stop_at); + } + + if ((_Size_bytes & size_t{0x10}) != 0) { // use original _Size_bytes; we've read only 32-byte chunks + const auto _Comparand = _Traits::_Set_neon_q(_Val); + _Rewind_bytes(_Last, 16); + const auto _Data = _Traits::_Load_q(_Last); + + const auto _Comparison = _Traits::_Cmp_neon_q(_Data, _Comparand); + + auto _Match = _Traits::_Mask_q(_Comparison); + if constexpr (_Pred == _Predicate::_Not_equal) { + _Match ^= 0xFFFF'FFFF'FFFF'FFFF; + } + + if (_Match != 0) { + const auto _Offset = _Get_last_h_pos_q(_Match); + _Advance_bytes(_Last, _Offset - (sizeof(_Ty) - 1)); + return _Last; + } + } + + if constexpr (sizeof(_Ty) < 8) { + if ((_Size_bytes & size_t{0x08}) != 0) { // use original _Size_bytes; we've read only 16/32-byte chunks + const auto _Comparand = _Traits::_Set_neon(_Val); + _Rewind_bytes(_Last, 8); + const auto _Data = _Traits::_Load(_Last); + + const auto _Comparison = _Traits::_Cmp_neon(_Data, _Comparand); + + auto _Match = _Traits::_Mask(_Comparison); + if constexpr (_Pred == _Predicate::_Not_equal) { + _Match ^= 0xFFFF'FFFF'FFFF'FFFF; + } + + if (_Match != 0) { + const auto _Offset = _Get_last_h_pos_d(_Match); + _Advance_bytes(_Last, _Offset - (sizeof(_Ty) - 1)); + return _Last; + } + } + } + + return _Find_last_scalar_tail<_Pred>(_First, _Last, _Real_last, _Val); + } #else // ^^^ defined(_M_ARM64) / !defined(_M_ARM64) vvv template const void* __stdcall _Find_impl(const void* _First, const void* const _Last, const _Ty _Val) noexcept { @@ -4459,7 +4581,7 @@ namespace { } template - const void* __stdcall _Find_last_impl(const void* _First, const void* _Last, const _Ty _Val) noexcept { + const void* __stdcall _Find_last_impl(const void* const _First, const void* _Last, const _Ty _Val) noexcept { const void* const _Real_last = _Last; #ifndef _M_ARM64EC const size_t _Size_bytes = _Byte_length(_First, _Last); @@ -4532,33 +4654,7 @@ namespace { } while (_Last != _Stop_at); } #endif // ^^^ !defined(_M_ARM64EC) ^^^ - auto _Ptr = static_cast(_Last); - for (;;) { - if (_Ptr == _First) { - return _Real_last; - } - --_Ptr; - if constexpr (_Pred == _Predicate::_Not_equal) { - if (*_Ptr != _Val) { - return _Ptr; - } - } else { - if (*_Ptr == _Val) { - return _Ptr; - } - } - } - } - - template - size_t __stdcall _Find_last_pos_impl( - const void* const _First, const void* const _Last, const _Ty _Val) noexcept { - const void* const _Result = _Find_last_impl<_Traits, _Pred>(_First, _Last, _Val); - if (_Result == _Last) { - return static_cast(-1); - } else { - return _Byte_length(_First, _Result) / sizeof(_Ty); - } + return _Find_last_scalar_tail<_Pred>(_First, _Last, _Real_last, _Val); } template @@ -4855,6 +4951,16 @@ namespace { } } #endif // ^^^ !defined(_M_ARM64) ^^^ + template + size_t __stdcall _Find_last_pos_impl( + const void* const _First, const void* const _Last, const _Ty _Val) noexcept { + const void* const _Result = _Find_last_impl<_Traits, _Pred>(_First, _Last, _Val); + if (_Result == _Last) { + return static_cast(-1); + } else { + return _Byte_length(_First, _Result) / sizeof(_Ty); + } + } } // namespace _Finding } // unnamed namespace @@ -4918,7 +5024,6 @@ const void* __stdcall __std_find_trivial_8( return _Finding::_Find_impl<_Finding::_Find_traits_8, _Finding::_Predicate::_Equal>(_First, _Last, _Val); } -#ifndef _M_ARM64 const void* __stdcall __std_find_last_trivial_1( const void* const _First, const void* const _Last, const uint8_t _Val) noexcept { return _Finding::_Find_last_impl<_Finding::_Find_traits_1, _Finding::_Predicate::_Equal>(_First, _Last, _Val); @@ -4938,7 +5043,6 @@ const void* __stdcall __std_find_last_trivial_8( const void* const _First, const void* const _Last, const uint64_t _Val) noexcept { return _Finding::_Find_last_impl<_Finding::_Find_traits_8, _Finding::_Predicate::_Equal>(_First, _Last, _Val); } -#endif // ^^^ !defined(_M_ARM64) ^^^ const void* __stdcall __std_find_not_ch_1( const void* const _First, const void* const _Last, const uint8_t _Val) noexcept { @@ -4960,7 +5064,6 @@ const void* __stdcall __std_find_not_ch_8( return _Finding::_Find_impl<_Finding::_Find_traits_8, _Finding::_Predicate::_Not_equal>(_First, _Last, _Val); } -#ifndef _M_ARM64 __declspec(noalias) size_t __stdcall __std_find_last_not_ch_pos_1( const void* const _First, const void* const _Last, const uint8_t _Val) noexcept { return _Finding::_Find_last_pos_impl<_Finding::_Find_traits_1, _Finding::_Predicate::_Not_equal>( @@ -4985,6 +5088,7 @@ __declspec(noalias) size_t __stdcall __std_find_last_not_ch_pos_8( _First, _Last, _Val); } +#ifndef _M_ARM64 const void* __stdcall __std_adjacent_find_1(const void* const _First, const void* const _Last) noexcept { return _Finding::_Adjacent_find_impl<_Finding::_Find_traits_1, uint8_t>(_First, _Last); } From 7f2aae1d0fd1f275cc4d34309dae6db0bf17ddb5 Mon Sep 17 00:00:00 2001 From: Hari Limaye Date: Mon, 2 Feb 2026 11:12:55 -0400 Subject: [PATCH 03/11] Add Neon implementation of `count` (#6049) --- stl/inc/xutility | 2 +- stl/src/vector_algorithms.cpp | 146 +++++++++++++++++++++++++++++++++- 2 files changed, 145 insertions(+), 3 deletions(-) diff --git a/stl/inc/xutility b/stl/inc/xutility index 8f5ef8e9207..f74264ac7c3 100644 --- a/stl/inc/xutility +++ b/stl/inc/xutility @@ -81,7 +81,7 @@ _STL_DISABLE_CLANG_WARNINGS #define _VECTORIZED_ADJACENT_FIND _VECTORIZED_FOR_X64_X86 #define _VECTORIZED_BITSET_FROM_STRING _VECTORIZED_FOR_X64_X86 #define _VECTORIZED_BITSET_TO_STRING _VECTORIZED_FOR_X64_X86 -#define _VECTORIZED_COUNT _VECTORIZED_FOR_X64_X86 +#define _VECTORIZED_COUNT _VECTORIZED_FOR_X64_X86_ARM64 #define _VECTORIZED_FIND _VECTORIZED_FOR_X64_X86_ARM64 #define _VECTORIZED_FIND_END _VECTORIZED_FOR_X64_X86 #define _VECTORIZED_FIND_FIRST_OF _VECTORIZED_FOR_X64_X86 diff --git a/stl/src/vector_algorithms.cpp b/stl/src/vector_algorithms.cpp index c1c2876306c..77568183b1d 100644 --- a/stl/src/vector_algorithms.cpp +++ b/stl/src/vector_algorithms.cpp @@ -5128,10 +5128,81 @@ const void* __stdcall __std_search_n_8( } // extern "C" -#ifndef _M_ARM64 namespace { namespace _Counting { -#ifdef _M_ARM64EC +#ifdef _M_ARM64 + struct _Count_traits_8 : _Finding::_Find_traits_8 { + static uint64x2_t _Sub(const uint64x2_t _Lhs, const uint64x2_t _Rhs) noexcept { + return vsubq_u64(_Lhs, _Rhs); + } + + static size_t _Reduce(const uint64x2_t _Val) noexcept { + return vgetq_lane_u64(vpaddq_u64(_Val, _Val), 0); + } + + static size_t _Reduce(const uint64x2_t _Val_lo, const uint64x2_t _Val_hi) noexcept { + return _Reduce(vaddq_u64(_Val_lo, _Val_hi)); + } + }; + + struct _Count_traits_4 : _Finding::_Find_traits_4 { + // Max value that will fit in 32-bit counters without overflow. + static constexpr size_t _Max_count = 0xFFFFFFFF; + + static uint32x4_t _Sub(const uint32x4_t _Lhs, const uint32x4_t _Rhs) noexcept { + return vsubq_u32(_Lhs, _Rhs); + } + + static size_t _Reduce(const uint32x4_t _Val) noexcept { + return vaddlvq_u32(_Val); + } + + static size_t _Reduce(const uint32x4_t _Val_lo, const uint32x4_t _Val_hi) noexcept { + uint64x2_t _Sum = vpaddlq_u32(_Val_lo); + _Sum = vpadalq_u32(_Sum, _Val_hi); + return _Count_traits_8::_Reduce(_Sum); + } + }; + + struct _Count_traits_2 : _Finding::_Find_traits_2 { + // Max value that will fit in 16-bit counters without overflow. + static constexpr size_t _Max_count = 0xFFFF; + + static uint16x8_t _Sub(const uint16x8_t _Lhs, const uint16x8_t _Rhs) noexcept { + return vsubq_u16(_Lhs, _Rhs); + } + + static size_t _Reduce(const uint16x8_t _Val) noexcept { + return vaddlvq_u16(_Val); + } + + static size_t _Reduce(const uint16x8_t _Val_lo, const uint16x8_t _Val_hi) noexcept { + uint32x4_t _Sum = vpaddlq_u16(_Val_lo); + _Sum = vpadalq_u16(_Sum, _Val_hi); + return _Count_traits_4::_Reduce(_Sum); + } + }; + + struct _Count_traits_1 : _Finding::_Find_traits_1 { + // Max value that will fit in 8-bit counters without overflow. + static constexpr size_t _Max_count = 0xFF; + + static uint8x16_t _Sub(const uint8x16_t _Lhs, const uint8x16_t _Rhs) noexcept { + return vsubq_u8(_Lhs, _Rhs); + } + + static size_t _Reduce(const uint8x16_t _Val) noexcept { + return vaddlvq_u8(_Val); + } + + static size_t _Reduce(const uint8x16_t _Val_lo, const uint8x16_t _Val_hi) noexcept { + uint16x8_t _Sum = vpaddlq_u8(_Val_lo); + _Sum = vpadalq_u8(_Sum, _Val_hi); + return _Count_traits_2::_Reduce(_Sum); + } + }; + +#elif defined(_M_ARM64EC) using _Count_traits_8 = void; using _Count_traits_4 = void; using _Count_traits_2 = void; @@ -5256,6 +5327,75 @@ namespace { }; #endif // ^^^ !defined(_M_ARM64EC) ^^^ +#ifdef _M_ARM64 + template + __declspec(noalias) size_t __stdcall _Count_impl( + const void* _First, const void* const _Last, const _Ty _Val) noexcept { + size_t _Result = 0; + const size_t _Size_bytes = _Byte_length(_First, _Last); + + if (size_t _Size = _Size_bytes & ~size_t{0x1F}; _Size != 0) { + const auto _Comparand = _Traits::_Set_neon_q(_Val); + const void* _Stop_at = _First; + + for (;;) { + if constexpr (sizeof(_Ty) >= sizeof(size_t)) { + _Advance_bytes(_Stop_at, _Size); + } else { + constexpr size_t _Max_portion_size = _Traits::_Max_count * 32; + const size_t _Portion_size = _Size < _Max_portion_size ? _Size : _Max_portion_size; + _Advance_bytes(_Stop_at, _Portion_size); + _Size -= _Portion_size; + } + + auto _Count_lo = _Traits::_Set_neon_q(0); + auto _Count_hi = _Traits::_Set_neon_q(0); + + do { + const auto _Data_lo = _Traits::_Load_q(static_cast(_First) + 0); + const auto _Data_hi = _Traits::_Load_q(static_cast(_First) + 16); + + const auto _Mask_lo = _Traits::_Cmp_neon_q(_Data_lo, _Comparand); + const auto _Mask_hi = _Traits::_Cmp_neon_q(_Data_hi, _Comparand); + _Count_lo = _Traits::_Sub(_Count_lo, _Mask_lo); + _Count_hi = _Traits::_Sub(_Count_hi, _Mask_hi); + _Advance_bytes(_First, 32); + } while (_First != _Stop_at); + + _Result += _Traits::_Reduce(_Count_lo, _Count_hi); + + if constexpr (sizeof(_Ty) >= sizeof(size_t)) { + break; + } else { + if (_Size == 0) { + break; + } + } + } + } + + if ((_Size_bytes & size_t{0x10}) != 0) { // use original _Size_bytes; we've read only 32-byte chunks + const auto _Comparand = _Traits::_Set_neon_q(_Val); + auto _Count_vector = _Traits::_Set_neon_q(0); + + const auto _Data = _Traits::_Load_q(_First); + const auto _Mask = _Traits::_Cmp_neon_q(_Data, _Comparand); + _Count_vector = _Traits::_Sub(_Count_vector, _Mask); + _Result += _Traits::_Reduce(_Count_vector); + + _Advance_bytes(_First, 16); + } + +// Avoid auto-vectorization of the scalar tail, as this is not beneficial for performance. +#pragma loop(no_vector) + for (auto _Ptr = static_cast(_First); _Ptr != _Last; ++_Ptr) { + if (*_Ptr == _Val) { + ++_Result; + } + } + return _Result; + } +#else // ^^^ defined(_M_ARM64) / !defined(_M_ARM64) vvv template __declspec(noalias) size_t __stdcall _Count_impl( const void* _First, const void* const _Last, const _Ty _Val) noexcept { @@ -5356,6 +5496,7 @@ namespace { } return _Result; } +#endif // ^^^ !defined(_M_ARM64) ^^^ } // namespace _Counting } // unnamed namespace @@ -5383,6 +5524,7 @@ __declspec(noalias) size_t __stdcall __std_count_trivial_8( } // extern "C" +#ifndef _M_ARM64 namespace { namespace _Find_meow_of { enum class _Predicate { _Any_of, _None_of }; From e1a1a1358a5a45f06bbdca578db05e9089d40446 Mon Sep 17 00:00:00 2001 From: Alex Guteniev Date: Mon, 2 Feb 2026 17:16:12 +0200 Subject: [PATCH 04/11] ``: add missed `function` in `move_only_function` coverage (#6031) --- .../test.cpp | 57 +++++++++++++------ 1 file changed, 40 insertions(+), 17 deletions(-) diff --git a/tests/std/tests/GH_005504_avoid_function_call_wrapping/test.cpp b/tests/std/tests/GH_005504_avoid_function_call_wrapping/test.cpp index 62e1a6ed6db..bd3df479759 100644 --- a/tests/std/tests/GH_005504_avoid_function_call_wrapping/test.cpp +++ b/tests/std/tests/GH_005504_avoid_function_call_wrapping/test.cpp @@ -103,13 +103,23 @@ void test_plain_call(const int expected_copies) { } template -void test_wrapped_call(const int expected_copies) { +void test_wrapped_move_call(const int expected_copies) { InnerWrapper inner{Callable{}}; OuterWrapper outer{move(inner)}; assert(!inner); assert(outer(copy_counter{}) == expected_copies); } +template +void test_wrapped_move_move_call(const int expected_copies) { + InnerWrapper inner{Callable{}}; + MiddleWrapper middle{move(inner)}; + OuterWrapper outer{move(middle)}; + assert(!inner); + assert(!middle); + assert(outer(copy_counter{}) == expected_copies); +} + template void test_wrapped_copy_call(const int expected_copies) { InnerWrapper inner{Callable{}}; @@ -155,25 +165,33 @@ int main() { alloc_checker{1}, test_plain_call, large_callable>(0); // Moves to the same - alloc_checker{0}, test_wrapped_call, function, small_callable>(0); - alloc_checker{1}, test_wrapped_call, function, large_callable>(0); + alloc_checker{0}, test_wrapped_move_call, function, small_callable>(0); + alloc_checker{1}, test_wrapped_move_call, function, large_callable>(0); alloc_checker{0}, test_wrapped_copy_call, function, small_callable>(0); alloc_checker{2}, test_wrapped_copy_call, function, large_callable>(0); - alloc_checker{0}, test_wrapped_call, move_only_function, small_callable>(0); - alloc_checker{1}, test_wrapped_call, move_only_function, large_callable>(0); + alloc_checker{0}, + test_wrapped_move_call, move_only_function, small_callable>(0); + alloc_checker{1}, + test_wrapped_move_call, move_only_function, large_callable>(0); // Abominables and noexcept specifier - alloc_checker{0}, test_wrapped_call, move_only_function, small_callable>(0); - alloc_checker{1}, test_wrapped_call, move_only_function, large_callable>(0); - alloc_checker{0}, test_wrapped_call, move_only_function, small_callable>(0); - alloc_checker{1}, test_wrapped_call, move_only_function, large_callable>(0); + alloc_checker{0}, + test_wrapped_move_call, move_only_function, small_callable>(0); + alloc_checker{1}, + test_wrapped_move_call, move_only_function, large_callable>(0); + alloc_checker{0}, + test_wrapped_move_call, move_only_function, small_callable>(0); + alloc_checker{1}, + test_wrapped_move_call, move_only_function, large_callable>(0); static_assert(!is_constructible_v, move_only_function>); static_assert(!is_constructible_v, move_only_function>); #ifdef __cpp_noexcept_function_type - alloc_checker{0}, test_wrapped_call, move_only_function, small_callable>(0); - alloc_checker{1}, test_wrapped_call, move_only_function, large_callable>(0); + alloc_checker{0}, + test_wrapped_move_call, move_only_function, small_callable>(0); + alloc_checker{1}, + test_wrapped_move_call, move_only_function, large_callable>(0); static_assert(!is_constructible_v, move_only_function>); #endif // defined(__cpp_noexcept_function_type) @@ -182,16 +200,21 @@ int main() { // Moves from function to move_only_function alloc_checker{is_64_bit ? 0 : 1}, - test_wrapped_call, function, small_callable>(0); - alloc_checker{1}, test_wrapped_call, function, large_callable>(0); + test_wrapped_move_call, function, small_callable>(0); + alloc_checker{1}, test_wrapped_move_call, function, large_callable>(0); + + alloc_checker{is_64_bit ? 0 : 1}, test_wrapped_move_move_call, + move_only_function, function, small_callable>(0); + alloc_checker{1}, test_wrapped_move_move_call, move_only_function, + function, large_callable>(0); // Moves from function to abominable move_only_function alloc_checker{is_64_bit ? 0 : 1}, - test_wrapped_call, function, small_callable>(0); - alloc_checker{1}, test_wrapped_call, function, large_callable>(0); + test_wrapped_move_call, function, small_callable>(0); + alloc_checker{1}, test_wrapped_move_call, function, large_callable>(0); alloc_checker{is_64_bit ? 0 : 1}, - test_wrapped_call, function, small_callable>(0); - alloc_checker{1}, test_wrapped_call, function, large_callable>(0); + test_wrapped_move_call, function, small_callable>(0); + alloc_checker{1}, test_wrapped_move_call, function, large_callable>(0); #ifdef __cpp_noexcept_function_type static_assert(!is_constructible_v, function>); From 50d29b5e8320e4f9ac665b50634005998b8baf0f Mon Sep 17 00:00:00 2001 From: "A. Jiang" Date: Mon, 2 Feb 2026 23:18:19 +0800 Subject: [PATCH 05/11] ``: Remove non-Standard inheritance from piecewise distributions (#6032) Co-authored-by: Stephan T. Lavavej --- stl/inc/random | 197 +++++++++++------- tests/std/test.lst | 1 + .../GH_001600_random_inheritance/env.lst | 4 + .../test.compile.pass.cpp | 78 +++++++ 4 files changed, 205 insertions(+), 75 deletions(-) create mode 100644 tests/std/tests/GH_001600_random_inheritance/env.lst create mode 100644 tests/std/tests/GH_001600_random_inheritance/test.compile.pass.cpp diff --git a/stl/inc/random b/stl/inc/random index f6d03829529..1b558903ea9 100644 --- a/stl/inc/random +++ b/stl/inc/random @@ -4082,9 +4082,8 @@ public: return _Pvec; } - void _Init(bool _Renorm = true) { // initialize - size_t _Size = _Pvec.size(); - size_t _Idx; + void _Init(const bool _Renorm = true) { // initialize + const size_t _Size = _Pvec.size(); if (_Renorm) { if (_Pvec.empty()) { @@ -4092,14 +4091,14 @@ public: } else { // normalize probabilities double _Sum = 0; - for (_Idx = 0; _Idx < _Size; ++_Idx) { // sum all probabilities + for (size_t _Idx = 0; _Idx < _Size; ++_Idx) { // sum all probabilities _STL_ASSERT(0.0 <= _Pvec[_Idx], "invalid probability for discrete_distribution"); _Sum += _Pvec[_Idx]; } _STL_ASSERT(0.0 < _Sum, "invalid probability vector for discrete_distribution"); if (_Sum != 1.0) { - for (_Idx = 0; _Idx < _Size; ++_Idx) { + for (size_t _Idx = 0; _Idx < _Size; ++_Idx) { _Pvec[_Idx] /= _Sum; } } @@ -4107,7 +4106,7 @@ public: } _Pcdf.assign(1, _Pvec[0]); - for (_Idx = 1; _Idx < _Size; ++_Idx) { + for (size_t _Idx = 1; _Idx < _Size; ++_Idx) { _Pcdf.push_back(_Pvec[_Idx] + _Pcdf[_Idx - 1]); } } @@ -4175,14 +4174,23 @@ public: void reset() noexcept /* strengthened */ {} // clear internal state + // reused by piecewise_constant_distribution and piecewise_linear_distribution + template + _NODISCARD static result_type _Invoke_param_pcdf(_Engine& _Eng, const _Myvec& _Pcdf) { + const double _Px = _STD _Nrand_impl(_Eng); + const auto _First = _Pcdf.begin(); + const auto _Position = _STD lower_bound(_First, _STD _Prev_iter(_Pcdf.end()), _Px); + return static_cast(_Position - _First); + } + template _NODISCARD result_type operator()(_Engine& _Eng) _DISTRIBUTION_CONST { - return _Eval(_Eng, _Par); + return _Invoke_param_pcdf(_Eng, _Par._Pcdf); } template _NODISCARD result_type operator()(_Engine& _Eng, const param_type& _Par0) _DISTRIBUTION_CONST { - return _Eval(_Eng, _Par0); + return _Invoke_param_pcdf(_Eng, _Par0._Pcdf); } _NODISCARD friend bool operator==(const discrete_distribution& _Left, const discrete_distribution& _Right) { @@ -4207,40 +4215,26 @@ public: return _Dist._Par._Write(_Ostr); } -private: - template - result_type _Eval(_Engine& _Eng, const param_type& _Par0) const { - double _Px = _Nrand_impl(_Eng); - const auto _First = _Par0._Pcdf.begin(); - const auto _Position = _STD lower_bound(_First, _Prev_iter(_Par0._Pcdf.end()), _Px); - return static_cast(_Position - _First); - } - -public: param_type _Par; }; _EXPORT_STD template -class piecewise_constant_distribution - : public discrete_distribution { // piecewise constant floating-point distribution - // TRANSITION: unused _Mypbase subobject from base class +class piecewise_constant_distribution { // piecewise constant floating-point distribution public: _RNG_REQUIRE_REALTYPE(piecewise_constant_distribution, _Ty); - using _Mybase = discrete_distribution; - using _Mypbase = typename _Mybase::param_type; using result_type = _Ty; - struct param_type : _Mypbase { // parameter package + struct param_type { // parameter package using distribution_type = piecewise_constant_distribution; param_type() : _Bvec{0, 1} {} template - param_type(_InIt1 _First1, _InIt1 _Last1, _InIt2 _First2) : _Mypbase(_Noinit), _Bvec(_First1, _Last1) { + param_type(_InIt1 _First1, _InIt1 _Last1, _InIt2 _First2) : _Base_params(_Noinit), _Bvec(_First1, _Last1) { if (2 <= _Bvec.size()) { for (size_t _Idx = 0; _Idx < _Bvec.size() - 1; ++_Idx) { - this->_Pvec.push_back(static_cast(*_First2++)); + _Base_params._Pvec.push_back(static_cast(*_First2++)); } } else { // default construct _Bvec = {0, 1}; @@ -4250,12 +4244,12 @@ public: } template - param_type(initializer_list<_Ty> _Ilist, _Fn _Func) : _Mypbase(_Noinit) { + param_type(initializer_list<_Ty> _Ilist, _Fn _Func) : _Base_params(_Noinit) { if (2 <= _Ilist.size()) { _Bvec.assign(_Ilist); for (size_t _Idx = 0; _Idx < _Bvec.size() - 1; ++_Idx) { - this->_Pvec.push_back(_Func(_Ty{0.5} * (_Bvec[_Idx] + _Bvec[_Idx + 1]))); + _Base_params._Pvec.push_back(_Func(_Ty{0.5} * (_Bvec[_Idx] + _Bvec[_Idx + 1]))); } } else { // default construct _Bvec = {0, 1}; @@ -4265,7 +4259,7 @@ public: } template - param_type(size_t _Count, _Ty _Low, _Ty _High, _Fn _Func) : _Mypbase(_Count, _Low, _High, _Func) { + param_type(size_t _Count, _Ty _Low, _Ty _High, _Fn _Func) : _Base_params(_Count, _Low, _High, _Func) { _Ty _Range = _High - _Low; if (_Count <= 0) { _Count = 1; @@ -4278,8 +4272,7 @@ public: } _NODISCARD friend bool operator==(const param_type& _Left, const param_type& _Right) { - return static_cast(_Left) == static_cast(_Right) - && _Left._Bvec == _Right._Bvec; + return _Left._Base_params == _Right._Base_params && _Left._Bvec == _Right._Bvec; } #if !_HAS_CXX20 @@ -4295,7 +4288,7 @@ public: #pragma warning(push) #pragma warning(disable : 4244) // '%s': conversion from '%s' to '%s', possible loss of data _NODISCARD vector<_Ty> densities() const { - vector<_Ty> _Ans(this->_Pvec.begin(), this->_Pvec.end()); + vector<_Ty> _Ans(_Base_params._Pvec.begin(), _Base_params._Pvec.end()); for (size_t _Idx = 0; _Idx < _Ans.size(); ++_Idx) { _Ans[_Idx] /= _Bvec[_Idx + 1] - _Bvec[_Idx]; @@ -4306,9 +4299,10 @@ public: #pragma warning(pop) void _Init() { // initialize - _Mypbase::_Init(); + _Base_params._Init(); } + discrete_distribution::param_type _Base_params; vector<_Ty> _Bvec; }; @@ -4376,10 +4370,11 @@ public: template friend basic_istream<_Elem, _Traits>& operator>>(basic_istream<_Elem, _Traits>& _Istr, piecewise_constant_distribution& _Dist) { // read state from _Istr - static_cast(_Dist._Par)._Read(_Istr); + _Dist._Par._Base_params._Read(_Istr); _Dist._Par._Bvec.clear(); - for (size_t _Idx = _Dist._Par._Pvec.size() + 1; 0 < _Idx; --_Idx) { // get a value and add to intervals vector + for (size_t _Idx = _Dist._Par._Base_params._Pvec.size() + 1; 0 < _Idx; --_Idx) { + // get a value and add to intervals vector double _Val; _In(_Istr, _Val); _Dist._Par._Bvec.push_back(_Val); @@ -4390,7 +4385,7 @@ public: template friend basic_ostream<_Elem, _Traits>& operator<<(basic_ostream<_Elem, _Traits>& _Ostr, const piecewise_constant_distribution& _Dist) { // write state to _Ostr - static_cast(_Dist._Par)._Write(_Ostr); + _Dist._Par._Base_params._Write(_Ostr); for (const auto& _Val : _Dist._Par._Bvec) { _Out(_Ostr, _Val); @@ -4401,39 +4396,36 @@ public: template result_type _Eval(_Engine& _Eng, const param_type& _Par0) _DISTRIBUTION_CONST { - size_t _Px = _Mybase::operator()(_Eng, _Par0); + size_t _Px = discrete_distribution::_Invoke_param_pcdf(_Eng, _Par0._Base_params._Pcdf); uniform_real_distribution<_Ty> _Dist(_Par0._Bvec[_Px], _Par0._Bvec[_Px + 1]); return _Dist(_Eng); } + discrete_distribution _Unused; // TRANSITION, ABI: this was a base class subobject param_type _Par; }; _EXPORT_STD template -class piecewise_linear_distribution - : public discrete_distribution { // piecewise linear floating-point distribution - // TRANSITION: unused _Mypbase subobject from base class +class piecewise_linear_distribution { // piecewise linear floating-point distribution public: _RNG_REQUIRE_REALTYPE(piecewise_linear_distribution, _Ty); - using _Mybase = discrete_distribution; - using _Mypbase = typename _Mybase::param_type; using result_type = _Ty; - struct param_type : _Mypbase { // parameter package - // TRANSITION, ABI: stores probability densities (N + 1 elements) in _Mybase::_Pvec - // this breaks invariants of discrete_distribution::param_type + struct param_type { // parameter package using distribution_type = piecewise_linear_distribution; - param_type() : _Bvec{0, 1} { - this->_Pvec.push_back(1.0); + param_type() { + _Init_base(); + _Pvec.push_back(1.0); + _Bvec = {0, 1}; } template - param_type(_InIt1 _First1, _InIt1 _Last1, _InIt2 _First2) : _Mypbase(_Noinit), _Bvec(_First1, _Last1) { + param_type(_InIt1 _First1, _InIt1 _Last1, _InIt2 _First2) : _Bvec(_First1, _Last1) { if (2 <= _Bvec.size()) { for (size_t _Idx = 0; _Idx < _Bvec.size(); ++_Idx) { - this->_Pvec.push_back(static_cast(*_First2++)); + _Pvec.push_back(static_cast(*_First2++)); } } else { // default construct _Bvec = {0, 1}; @@ -4443,12 +4435,12 @@ public: } template - param_type(initializer_list<_Ty> _Ilist, _Fn _Func) : _Mypbase(_Noinit) { + param_type(initializer_list<_Ty> _Ilist, _Fn _Func) { if (2 <= _Ilist.size()) { _Bvec.assign(_Ilist); for (const auto& _Bval : _Bvec) { - this->_Pvec.push_back(_Func(_Bval)); + _Pvec.push_back(_Func(_Bval)); } } else { // default construct _Bvec = {0, 1}; @@ -4458,7 +4450,7 @@ public: } template - param_type(size_t _Count, _Ty _Low, _Ty _High, _Fn _Func) : _Mypbase(_Noinit) { + param_type(size_t _Count, _Ty _Low, _Ty _High, _Fn _Func) { _Ty _Range = _High - _Low; _STL_ASSERT(_Ty{0} < _Range, "invalid range for piecewise_linear_distribution"); if (_Count < 1) { @@ -4469,14 +4461,13 @@ public: for (size_t _Idx = 0; _Idx <= _Count; ++_Idx) { // compute _Bvec and _Pvec _Ty _Bval = _Low + _Idx * _Range; _Bvec.push_back(_Bval); - this->_Pvec.push_back(_Func(_Bval)); + _Pvec.push_back(_Func(_Bval)); } _Init(); } _NODISCARD friend bool operator==(const param_type& _Left, const param_type& _Right) { - return static_cast(_Left) == static_cast(_Right) - && _Left._Bvec == _Right._Bvec; + return _Left._Pvec == _Right._Pvec && _Left._Bvec == _Right._Bvec; } #if !_HAS_CXX20 @@ -4492,47 +4483,101 @@ public: #pragma warning(push) #pragma warning(disable : 4244) // '%s': conversion from '%s' to '%s', possible loss of data _NODISCARD vector<_Ty> densities() const { - vector<_Ty> _Ans(this->_Pvec.begin(), this->_Pvec.end()); + vector<_Ty> _Ans(_Pvec.begin(), _Pvec.end()); return _Ans; } #pragma warning(pop) _NODISCARD double _Piece_probability(const size_t _Idx) const { - return 0.5 * (this->_Pvec[_Idx] + this->_Pvec[_Idx + 1]) - * static_cast(_Bvec[_Idx + 1] - _Bvec[_Idx]); + return 0.5 * (_Pvec[_Idx] + _Pvec[_Idx + 1]) * static_cast(_Bvec[_Idx + 1] - _Bvec[_Idx]); + } + + void _Init_base(const bool _Renorm = true) { // initialize like discrete_distribution::param_type + const size_t _Size = _Pvec.size(); + + if (_Renorm) { + if (_Pvec.empty()) { + _Pvec.push_back(1.0); // make empty vector degenerate + } else { // normalize probabilities + double _Sum = 0; + + for (size_t _Idx = 0; _Idx < _Size; ++_Idx) { // sum all probabilities + _STL_ASSERT(0.0 <= _Pvec[_Idx], "invalid probability for piecewise_linear_distribution"); + _Sum += _Pvec[_Idx]; + } + + _STL_ASSERT(0.0 < _Sum, "invalid probability vector for piecewise_linear_distribution"); + if (_Sum != 1.0) { + for (size_t _Idx = 0; _Idx < _Size; ++_Idx) { + _Pvec[_Idx] /= _Sum; + } + } + } + } + + _Pcdf.assign(1, _Pvec[0]); + for (size_t _Idx = 1; _Idx < _Size; ++_Idx) { + _Pcdf.push_back(_Pvec[_Idx] + _Pcdf[_Idx - 1]); + } } - void _Init(bool _Renorm = true) { // initialize - size_t _Size = this->_Pvec.size(); - size_t _Idx; + void _Init(const bool _Renorm = true) { // initialize + const size_t _Size = _Pvec.size(); if (_Renorm) { - if (this->_Pvec.empty()) { // make empty vector degenerate - this->_Pvec = {1.0, 1.0}; + if (_Pvec.empty()) { // make empty vector degenerate + _Pvec = {1.0, 1.0}; } else { // normalize probabilities double _Sum = 0; - _STL_ASSERT(0.0 <= this->_Pvec[0], "invalid probability for piecewise_linear_distribution"); - for (_Idx = 1; _Idx < _Size; ++_Idx) { // sum all probabilities - _STL_ASSERT(0.0 <= this->_Pvec[_Idx], "invalid probability for piecewise_linear_distribution"); + _STL_ASSERT(0.0 <= _Pvec[0], "invalid probability for piecewise_linear_distribution"); + for (size_t _Idx = 1; _Idx < _Size; ++_Idx) { // sum all probabilities + _STL_ASSERT(0.0 <= _Pvec[_Idx], "invalid probability for piecewise_linear_distribution"); _Sum += _Piece_probability(_Idx - 1); } _STL_ASSERT(0.0 < _Sum, "invalid probability vector for piecewise_linear_distribution"); if (_Sum != 1.0) { - for (_Idx = 0; _Idx < _Size; ++_Idx) { - this->_Pvec[_Idx] /= _Sum; + for (size_t _Idx = 0; _Idx < _Size; ++_Idx) { + _Pvec[_Idx] /= _Sum; } } } } - this->_Pcdf.assign(1, _Piece_probability(0)); - for (_Idx = 2; _Idx < _Size; ++_Idx) { - this->_Pcdf.push_back(_Piece_probability(_Idx - 1) + this->_Pcdf[_Idx - 2]); + _Pcdf.assign(1, _Piece_probability(0)); + for (size_t _Idx = 2; _Idx < _Size; ++_Idx) { + _Pcdf.push_back(_Piece_probability(_Idx - 1) + _Pcdf[_Idx - 2]); + } + } + + template + basic_istream<_Elem, _Traits>& _Read(basic_istream<_Elem, _Traits>& _Istr) { // read state from _Istr + size_t _Nvals; + _Istr >> _Nvals; + _Pvec.clear(); + for (; 0 < _Nvals; --_Nvals) { // get a value and add to vector + double _Val; + _In(_Istr, _Val); + _Pvec.push_back(_Val); + } + _Init_base(false); // don't renormalize, just compute CDF + return _Istr; + } + + template + basic_ostream<_Elem, _Traits>& _Write(basic_ostream<_Elem, _Traits>& _Ostr) const { // write state to _Ostr + _Ostr << ' ' << _Pvec.size(); + + for (const auto& _Val : _Pvec) { + _Out(_Ostr, _Val); } + + return _Ostr; } + vector _Pvec; + vector _Pcdf; vector<_Ty> _Bvec; }; @@ -4600,10 +4645,11 @@ public: template friend basic_istream<_Elem, _Traits>& operator>>(basic_istream<_Elem, _Traits>& _Istr, piecewise_linear_distribution& _Dist) { // read state from _Istr - static_cast(_Dist._Par)._Read(_Istr); + _Dist._Par._Read(_Istr); _Dist._Par._Bvec.clear(); - for (size_t _Idx = _Dist._Par._Pvec.size(); 0 < _Idx; --_Idx) { // get a value and add to intervals vector + for (size_t _Idx = _Dist._Par._Pvec.size(); 0 < _Idx; --_Idx) { + // get a value and add to intervals vector double _Val; _In(_Istr, _Val); _Dist._Par._Bvec.push_back(_Val); @@ -4615,7 +4661,7 @@ public: template friend basic_ostream<_Elem, _Traits>& operator<<(basic_ostream<_Elem, _Traits>& _Ostr, const piecewise_linear_distribution& _Dist) { // write state to _Ostr - static_cast(_Dist._Par)._Write(_Ostr); + _Dist._Par._Write(_Ostr); for (const auto& _Val : _Dist._Par._Bvec) { _Out(_Ostr, _Val); @@ -4626,7 +4672,7 @@ public: template result_type _Eval(_Engine& _Eng, const param_type& _Par0) _DISTRIBUTION_CONST { - size_t _Px = _Mybase::operator()(_Eng, _Par0); + size_t _Px = discrete_distribution::_Invoke_param_pcdf(_Eng, _Par0._Pcdf); double _Px0 = _Par0._Pvec[_Px]; double _Px1 = _Par0._Pvec[_Px + 1]; uniform_real_distribution<_Ty> _Dist; @@ -4640,6 +4686,7 @@ public: return _Par0._Bvec[_Px] + _Xx0 * (_Par0._Bvec[_Px + 1] - _Par0._Bvec[_Px]); } + discrete_distribution _Unused; // TRANSITION, ABI: this was a base class subobject param_type _Par; }; diff --git a/tests/std/test.lst b/tests/std/test.lst index 5bb68adcb1d..85d7fde5fe5 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -202,6 +202,7 @@ tests\GH_001411_core_headers tests\GH_001530_binomial_accuracy tests\GH_001541_case_sensitive_boolalpha tests\GH_001596_adl_proof_algorithms +tests\GH_001600_random_inheritance tests\GH_001638_dllexport_derived_classes tests\GH_001850_clog_tied_to_cout tests\GH_001858_iostream_exception diff --git a/tests/std/tests/GH_001600_random_inheritance/env.lst b/tests/std/tests/GH_001600_random_inheritance/env.lst new file mode 100644 index 00000000000..19f025bd0e6 --- /dev/null +++ b/tests/std/tests/GH_001600_random_inheritance/env.lst @@ -0,0 +1,4 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +RUNALL_INCLUDE ..\usual_matrix.lst diff --git a/tests/std/tests/GH_001600_random_inheritance/test.compile.pass.cpp b/tests/std/tests/GH_001600_random_inheritance/test.compile.pass.cpp new file mode 100644 index 00000000000..977fa0c19b0 --- /dev/null +++ b/tests/std/tests/GH_001600_random_inheritance/test.compile.pass.cpp @@ -0,0 +1,78 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include + +#define STATIC_ASSERT(...) static_assert(__VA_ARGS__, #__VA_ARGS__) + +using namespace std; + +// GH-1600 ": piecewise_linear_distribution::param_type should not inherit from +// discrete_distribution::param_type" +// Note that inheritance between distribution types should also be removed. +template +void test_gh_1600_inheritance() { + using Con = piecewise_constant_distribution; + using Lin = piecewise_linear_distribution; + using Dis = discrete_distribution; + + using ConParam = typename Con::param_type; + using LinParam = typename Lin::param_type; + using DisParam = Dis::param_type; + + STATIC_ASSERT(!is_base_of_v); + STATIC_ASSERT(!is_base_of_v); + + STATIC_ASSERT(!is_base_of_v); + STATIC_ASSERT(!is_base_of_v); +} + +// Until 2026-01, piecewise_constant_distribution and piecewise_linear_distribution +// were both derived from discrete_distribution. Same for their param_type structs. +// Now the discrete_distribution objects are changed from base class subobjects to leading member subobjects. +// Same for piecewise_constant_distribution::param_type. +// For piecewise_linear_distribution::param_type, the original discrete_distribution::param_type subobject is +// decomposed into vectors to avoid imposing invariants. +// In vNext, we should probably remove unused members, so the sizes will be reduced. +template +void test_gh_1600_abi() { + // piecewise_constant_distribution, piecewise_linear_distribution, and their param_type structs + // don't introduce padding bytes under MSVC ABI. So it's OK to just add the sizes. + + // The sizes will probably be reduced in vNext. + + using Con = piecewise_constant_distribution; + using Lin = piecewise_linear_distribution; + using Dis = discrete_distribution; + + using ConParam = typename Con::param_type; + using LinParam = typename Lin::param_type; + using DisParam = Dis::param_type; + + STATIC_ASSERT(sizeof(Con) == sizeof(Dis) + sizeof(ConParam)); + STATIC_ASSERT(sizeof(ConParam) == sizeof(DisParam) + sizeof(vector)); + + STATIC_ASSERT(sizeof(Lin) == sizeof(Dis) + sizeof(LinParam)); + STATIC_ASSERT(sizeof(LinParam) == sizeof(DisParam) + sizeof(vector)); + + // The alignments are likely to be unchanged in vNext. + + STATIC_ASSERT(alignof(Con) == alignof(void*)); + STATIC_ASSERT(alignof(ConParam) == alignof(void*)); + + STATIC_ASSERT(alignof(Lin) == alignof(void*)); + STATIC_ASSERT(alignof(LinParam) == alignof(void*)); +} + +void test() { + test_gh_1600_inheritance(); + test_gh_1600_inheritance(); + test_gh_1600_inheritance(); + + test_gh_1600_abi(); + test_gh_1600_abi(); + test_gh_1600_abi(); +} From f5094173edf2e8f87c5be26232f5affb7ac56d3f Mon Sep 17 00:00:00 2001 From: Alex Guteniev Date: Mon, 2 Feb 2026 17:23:33 +0200 Subject: [PATCH 06/11] Document some vector algorithms structure (#6039) Co-authored-by: Stephan T. Lavavej --- stl/src/vector_algorithms.cpp | 80 +++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/stl/src/vector_algorithms.cpp b/stl/src/vector_algorithms.cpp index 77568183b1d..6d4d9ddb5f4 100644 --- a/stl/src/vector_algorithms.cpp +++ b/stl/src/vector_algorithms.cpp @@ -248,6 +248,22 @@ void* __cdecl __std_swap_ranges_trivially_swappable( namespace { namespace _Rotating { + // The 'rotate' algorithm can be implemented: + // - using 'reverse' on the parts and the whole + // - using 'swap_ranges' repeatedly + // If both are vectorized, the latter is generally faster, due to avoiding extra swizzling. + // + // On top of 'swap_ranges' the following optimizations are made: + // - using a temporary buffer (on the stack), if one of the parts is small enough + // - 'swap_ranges' with more than two ranges + // + // When swapping multiple ranges, the more ranges the fewer operations, however: + // - determining the number of ranges at runtime will require more instructions to manage it + // - adding more code paths will spend more instruction cache and will fail branch prediction more often + // - going way too far with the number of ranges may interfere with prefetching + // + // We implement the 'swap_ranges' approach with a small temporary buffer and swapping three ranges. + #ifdef _M_ARM64 void __forceinline _Swap_3_ranges(void* _First1, void* const _Last1, void* _First2, void* _First3) noexcept { if (_Byte_length(_First1, _Last1) >= 64) { @@ -923,6 +939,15 @@ __declspec(noalias) void __cdecl __std_reverse_copy_trivially_copyable_8( namespace { namespace _Sorting { + // The 'minmax' and 'minmax_element' algorithms first compare vectors against other vectors + // ("vertical" comparisons), then elements of the same vector ("horizontal" comparisons). + // For 'minmax' that's it. + // + // 'minmax_element' needs to track element positions, so it has a vector of integers that is incremented, + // and min/max indices vectors. + // Since small integer indices for small elements may overflow, we sometimes have to do the horizontal part + // more often than just once at the end. + enum _Min_max_mode { _Mode_min = 1 << 0, _Mode_max = 1 << 1, @@ -4785,6 +4810,9 @@ namespace { _MskX = (_MskX >> sizeof(_Ty)) & _MskX; + // Use __ull_rshift for better codegen in 32-bit mode, assuming the shifts are small. + // In 64-bit mode, __ull_rshift works exactly the same as the right shift operator. + if constexpr (sizeof(_Ty) == 1) { _MskX = __ull_rshift(_MskX, _Sh1) & _MskX; } @@ -5527,10 +5555,33 @@ __declspec(noalias) size_t __stdcall __std_count_trivial_8( #ifndef _M_ARM64 namespace { namespace _Find_meow_of { + // 'find_meow_of' is a quadratic complexity algorithm. + // Quadratic vectorization: + // - SSE4.2 for 8-bit and 16-bit elements: _mm_cmpestri + // - AVX2 for 32-bit and 64-bit elements: shuffle elements so that each is tried in each position, + // find the min/max match position + // + // But for a needle with elements in a small range, a bitmap can be used, making the algorithm linear. + // We consider that only for 'basic_string'/'basic_string_view', not for 'std::find_first_of'. + // We consider bitmaps for elements in the range [0, 255]. + // + // We have two different bitmap algorithms: AVX2 and scalar. + // The decision is based on needle and haystack lengths, see _Pick_strategy. + enum class _Predicate { _Any_of, _None_of }; #ifndef _M_ARM64EC namespace _Bitmap_details { + // AVX2 bitmap: __m256i value with each bit corresponding to a needle element. Set bits mean "present". + // + // The bitmap algorithm implemented in _Bitmap_step: + // - Process by 8 elements, populate them as in 32-bit values vector, + // regardless of the original element size + // - Split the low 5 bits and high 3 bits of these elements + // - Use the high 3 bits with _mm256_permutevar8x32_epi32 to find 32-bit bitmap portion for each element + // - Use the low 5 bits to shift the bitmap portion, so that the bitmap bit corresponding to them is on + // highest position. Negate these low 5 bits before that, as we're populating the highest position + // - The resulting mask can later be converted via _mm256_movemask_ps to one byte bitmap __m256i _Bitmap_step(const __m256i _Bitmap, const __m256i _Data) noexcept { const __m256i _Data_high = _mm256_srli_epi32(_Data, 5); const __m256i _Bitmap_parts = _mm256_permutevar8x32_epi32(_Bitmap, _Data_high); @@ -5751,6 +5802,10 @@ namespace { } } + // The bitmap takes time to setup (especially AVX2), but it's linear and fast (especially AVX2, again), so: + // - with a small amount of total iterations, the vectorized quadratic algorithm wins over bitmaps + // - with a small haystack, among bitmaps the scalar bitmap is preferred, even if AVX2 is available + enum class _Strategy { _No_bitmap, _Scalar_bitmap, _Vector_bitmap }; template @@ -5899,6 +5954,8 @@ namespace { } #endif // ^^^ !defined(_M_ARM64EC) ^^^ + // Scalar bitmap: bools, not really compressed to bits, for faster building and faster access. + // For sizes above integers but fitting within cache, this approach wins. using _Scalar_table_t = bool[256]; template @@ -6861,6 +6918,18 @@ __declspec(noalias) size_t __stdcall __std_find_last_not_of_trivial_pos_2(const namespace { namespace _Find_seq { + // The caveat in the 'search' and 'find_end' optimization is that this pattern would be inefficient: + // for (auto i = hay_begin; i != hay_end; ++i) { + // if (memcmp(i, needle, size) == 0) { return i; } + // } + // because the mismatch usually happens early, so memcmp would typically be slower than a simple loop. + // + // The solution is: + // - in outer loop, do the 'find'-like thing, but preserve the setup (i.e. vector with first needle element) + // - in inner loop, try to compare with readily available needle in a register, + // or at least with the needle start, if the needle is long, to fail early mismatches early. + // Or use SSE4.2 _mm_cmpestri, which can be good too, especially for 8-bit forward search. + #ifdef _M_ARM64EC using _Find_seq_traits_avx_1 = void; using _Find_seq_traits_avx_2 = void; @@ -8079,6 +8148,17 @@ __declspec(noalias) void __stdcall __std_replace_copy_8(const void* const _First namespace { namespace _Removing { + // 'remove' and 'unique': form bit mask based on matches, then do _mm_shuffle_epi8/_mm256_permutevar8x32_epi32 + // to the destination with removed matches; the shuffle pattern is taken from a lookup table using the bit mask. + // After writing to dest, shift the dest pointer by the mismatch count. + // There will be redundant elements written, and they will be subsequently overwritten by overlapped writes. + // + // 'unique': the bit mask is formed by comparing against shifted self. 'adjacent_find' must precede this + // to avoid a load overlapping a just stored vector (store buffer stall). + // + // Non '_copy' flavors: store directly, the data past the returned iterator must be ignored anyway. + // '_copy' flavors: can't write more than expected, use intermediate buffer and then copy to dest. + template void* _Remove_fallback( const void* const _First, const void* const _Last, void* const _Out, const _Ty _Val) noexcept { From 0fe0545f3bd3c9f72d2890261209325cd0cbb608 Mon Sep 17 00:00:00 2001 From: Alex Guteniev Date: Mon, 2 Feb 2026 17:24:30 +0200 Subject: [PATCH 07/11] ``: comment on `FILE*` ownership (#6043) --- stl/inc/__msvc_filebuf.hpp | 2 +- stl/inc/fstream | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/stl/inc/__msvc_filebuf.hpp b/stl/inc/__msvc_filebuf.hpp index 009265e0a19..fd6069db015 100644 --- a/stl/inc/__msvc_filebuf.hpp +++ b/stl/inc/__msvc_filebuf.hpp @@ -138,7 +138,7 @@ class basic_filebuf : public basic_streambuf<_Elem, _Traits> { // stream buffer _Init(nullptr, _Newfl); } - explicit basic_filebuf(FILE* const _File) : _Mysb() { // extension + explicit basic_filebuf(FILE* const _File) : _Mysb() { // extension, no ownership taking _Init(_File, _Newfl); } diff --git a/stl/inc/fstream b/stl/inc/fstream index cbae7586dbc..35d48b24fe2 100644 --- a/stl/inc/fstream +++ b/stl/inc/fstream @@ -61,7 +61,8 @@ public: : basic_ifstream(_Path.c_str(), _Mode, _Prot) {} // _Prot is an extension #endif // _HAS_CXX17 - explicit basic_ifstream(FILE* _File) : _Mybase(_STD addressof(_Filebuffer)), _Filebuffer(_File) {} // extension + explicit basic_ifstream(FILE* _File) + : _Mybase(_STD addressof(_Filebuffer)), _Filebuffer(_File) {} // extension, no ownership taking basic_ifstream(basic_ifstream&& _Right) : _Mybase(_STD addressof(_Filebuffer)) { _Assign_rv(_STD move(_Right)); @@ -207,7 +208,8 @@ public: : basic_ofstream(_Path.c_str(), _Mode, _Prot) {} // _Prot is an extension #endif // _HAS_CXX17 - explicit basic_ofstream(FILE* _File) : _Mybase(_STD addressof(_Filebuffer)), _Filebuffer(_File) {} // extension + explicit basic_ofstream(FILE* _File) + : _Mybase(_STD addressof(_Filebuffer)), _Filebuffer(_File) {} // extension, no ownership taking basic_ofstream(basic_ofstream&& _Right) : _Mybase(_STD addressof(_Filebuffer)) { _Assign_rv(_STD move(_Right)); @@ -358,7 +360,8 @@ public: : basic_fstream(_Path.c_str(), _Mode, _Prot) {} // _Prot is an extension #endif // _HAS_CXX17 - explicit basic_fstream(FILE* _File) : _Mybase(_STD addressof(_Filebuffer)), _Filebuffer(_File) {} // extension + explicit basic_fstream(FILE* _File) + : _Mybase(_STD addressof(_Filebuffer)), _Filebuffer(_File) {} // extension, no ownership taking basic_fstream(basic_fstream&& _Right) : _Mybase(_STD addressof(_Filebuffer)) { _Assign_rv(_STD move(_Right)); From 793b6f2e26cb784ed25c6e5736cd2a4962b08b4f Mon Sep 17 00:00:00 2001 From: "S. B. Tam" Date: Mon, 2 Feb 2026 23:27:47 +0800 Subject: [PATCH 08/11] Fix off-by-one error in `__std_get_cvt` (#6059) Co-authored-by: AZero13 --- stl/src/format.cpp | 2 +- .../test.cpp | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/stl/src/format.cpp b/stl/src/format.cpp index 67a91356a9d..d639183985a 100644 --- a/stl/src/format.cpp +++ b/stl/src/format.cpp @@ -31,7 +31,7 @@ extern "C" [[nodiscard]] __std_win_error __stdcall __std_get_cvt( break; } - for (unsigned char _First = _Info.LeadByte[_Idx], _Last = _Info.LeadByte[_Idx + 1]; _First != _Last; ++_First) { + for (unsigned int _First = _Info.LeadByte[_Idx], _Last = _Info.LeadByte[_Idx + 1]; _First <= _Last; ++_First) { _Pcvt->_Isleadbyte[_First >> 3] |= 1u << (_First & 0b111u); } } diff --git a/tests/std/tests/P2286R8_text_formatting_escaping_legacy_text_encoding/test.cpp b/tests/std/tests/P2286R8_text_formatting_escaping_legacy_text_encoding/test.cpp index b01f72fae10..44ac16c52e4 100644 --- a/tests/std/tests/P2286R8_text_formatting_escaping_legacy_text_encoding/test.cpp +++ b/tests/std/tests/P2286R8_text_formatting_escaping_legacy_text_encoding/test.cpp @@ -15,6 +15,8 @@ void test_escaped_string() { assert(format("{:?}", "\xEB!") == "\"\\x{eb}!\""); assert(format("{:?}", "\x81\x40\x40\x81") == "\"\\u{3000}\x40\\x{81}\""); + + assert(format("{:?}", "\xFC\x4B") == "\"\u9ED1\""); } template From 3f638e3f53b3a224e660cf2dbe921d620ebb8761 Mon Sep 17 00:00:00 2001 From: "A. Jiang" Date: Mon, 2 Feb 2026 23:31:30 +0800 Subject: [PATCH 09/11] Implement LWG-3090 What is [time.duration.cons]/4's "no overflow is induced in the conversion" intended to mean? (#6050) Co-authored-by: Stephan T. Lavavej --- stl/inc/__msvc_chrono.hpp | 6 +- .../tests/P0092R1_polishing_chrono/test.cpp | 74 +++++++++++++++++++ 2 files changed, 78 insertions(+), 2 deletions(-) diff --git a/stl/inc/__msvc_chrono.hpp b/stl/inc/__msvc_chrono.hpp index 44a0e6a7d10..b4368dd9530 100644 --- a/stl/inc/__msvc_chrono.hpp +++ b/stl/inc/__msvc_chrono.hpp @@ -129,8 +129,10 @@ namespace chrono { : _MyRep(static_cast<_Rep>(_Val)) {} template - || (_Ratio_divide_sfinae<_Period2, _Period>::den == 1 && !treat_as_floating_point_v<_Rep2>), + enable_if_t + && (treat_as_floating_point_v<_Rep> + || (_Ratio_divide_sfinae<_Period2, _Period>::den == 1 + && !treat_as_floating_point_v<_Rep2>) ), int> = 0> constexpr duration(const duration<_Rep2, _Period2>& _Dur) noexcept(is_arithmetic_v<_Rep> && is_arithmetic_v<_Rep2>) // strengthened diff --git a/tests/std/tests/P0092R1_polishing_chrono/test.cpp b/tests/std/tests/P0092R1_polishing_chrono/test.cpp index 6c912abfd50..752d80acd89 100644 --- a/tests/std/tests/P0092R1_polishing_chrono/test.cpp +++ b/tests/std/tests/P0092R1_polishing_chrono/test.cpp @@ -5,10 +5,12 @@ // Tests the new functions added as part of P0092R1, "Polishing Chrono" // +#include <__msvc_int128.hpp> // an integer-class type should emulate an arithmetic type, see also GH-1909 #include #include #include #include +#include using namespace std; using namespace std::chrono; @@ -188,6 +190,75 @@ STATIC_ASSERT(floor(tp1).time_since_epoch().count() == 1); STATIC_ASSERT(ceil(tp1).time_since_epoch().count() == 2); STATIC_ASSERT(round(tp1).time_since_epoch().count() == 2); +// Test LWG-3090 "What is [time.duration.cons]/4's "no overflow is induced in the conversion" intended to mean?" +constexpr bool test_lwg_3090() { + STATIC_ASSERT(is_constructible_v, seconds>); + STATIC_ASSERT(is_constructible_v, const seconds&>); + STATIC_ASSERT(is_convertible_v>); + STATIC_ASSERT(is_convertible_v>); + + STATIC_ASSERT(!is_constructible_v>); + STATIC_ASSERT(!is_constructible_v&>); + STATIC_ASSERT(!is_convertible_v, seconds>); + STATIC_ASSERT(!is_convertible_v&, seconds>); + + STATIC_ASSERT(is_constructible_v, minutes>); + STATIC_ASSERT(is_constructible_v, const minutes&>); + STATIC_ASSERT(is_convertible_v>); + STATIC_ASSERT(is_convertible_v>); + + STATIC_ASSERT(!is_constructible_v>); + STATIC_ASSERT(!is_constructible_v&>); + STATIC_ASSERT(!is_convertible_v, minutes>); + STATIC_ASSERT(!is_convertible_v&, minutes>); + + STATIC_ASSERT(!is_constructible_v, milliseconds>); + STATIC_ASSERT(!is_constructible_v, const milliseconds&>); + STATIC_ASSERT(!is_convertible_v>); + STATIC_ASSERT(!is_convertible_v>); + + STATIC_ASSERT(!is_constructible_v>); + STATIC_ASSERT(!is_constructible_v&>); + STATIC_ASSERT(!is_convertible_v, milliseconds>); + STATIC_ASSERT(!is_convertible_v&, milliseconds>); + + STATIC_ASSERT(is_constructible_v, seconds>); + STATIC_ASSERT(is_constructible_v, const seconds&>); + STATIC_ASSERT(is_convertible_v>); + STATIC_ASSERT(is_convertible_v>); + + STATIC_ASSERT(!is_constructible_v>); + STATIC_ASSERT(!is_constructible_v&>); + STATIC_ASSERT(!is_convertible_v, seconds>); + STATIC_ASSERT(!is_convertible_v&, seconds>); + + STATIC_ASSERT(is_constructible_v, minutes>); + STATIC_ASSERT(is_constructible_v, const minutes&>); + STATIC_ASSERT(is_convertible_v>); + STATIC_ASSERT(is_convertible_v>); + + STATIC_ASSERT(!is_constructible_v>); + STATIC_ASSERT(!is_constructible_v&>); + STATIC_ASSERT(!is_convertible_v, minutes>); + STATIC_ASSERT(!is_convertible_v&, minutes>); + + STATIC_ASSERT(!is_constructible_v, milliseconds>); + STATIC_ASSERT(!is_constructible_v, const milliseconds&>); + STATIC_ASSERT(!is_convertible_v>); + STATIC_ASSERT(!is_convertible_v>); + + STATIC_ASSERT(!is_constructible_v>); + STATIC_ASSERT(!is_constructible_v&>); + STATIC_ASSERT(!is_convertible_v, milliseconds>); + STATIC_ASSERT(!is_convertible_v&, milliseconds>); + + assert(duration<_Signed128>{seconds{1}}.count() == 1); + assert(duration<_Signed128>{minutes{12}}.count() == 720); + assert(duration<_Unsigned128>{seconds{123}}.count() == 123); + assert(duration<_Unsigned128>{minutes{1234}}.count() == 74040); + + return true; +} int overloaded(milliseconds) { return 11; @@ -222,4 +293,7 @@ int main() { assert(overloaded(40ms) == 11); assert(overloaded(50s) == 22); assert(overloaded(duration(60)) == 33); + + STATIC_ASSERT(test_lwg_3090()); + test_lwg_3090(); } From 7f083b99a4e3c236720b0cc5cd3b6f626dae7a2e Mon Sep 17 00:00:00 2001 From: "A. Jiang" Date: Mon, 2 Feb 2026 23:33:56 +0800 Subject: [PATCH 10/11] Implement LWG-3436 `std::construct_at` should support arrays (#5920) Co-authored-by: Stephan T. Lavavej --- stl/inc/memory | 23 +++++- stl/inc/xutility | 42 ++++++++++- .../test.cpp | 74 +++++++++++++++++++ .../tests/P1004R2_constexpr_vector/test.cpp | 40 ++++++++++ 4 files changed, 175 insertions(+), 4 deletions(-) diff --git a/stl/inc/memory b/stl/inc/memory index 7f110c0bc5c..55e08b73bac 100644 --- a/stl/inc/memory +++ b/stl/inc/memory @@ -533,7 +533,7 @@ namespace ranges { struct _Construct_at_fn { template - requires requires(_Ty* _Ptr, _Types&&... _Args) { + requires (!is_unbounded_array_v<_Ty>) && requires(_Ty* _Ptr, _Types&&... _Args) { ::new (static_cast(_Ptr)) _Ty(static_cast<_Types &&>(_Args)...); // per LWG-3888 } _STATIC_CALL_OPERATOR constexpr _Ty* operator()(_Ty* _Location, _Types&&... _Args) _CONST_CALL_OPERATOR @@ -542,7 +542,26 @@ namespace ranges { #ifdef __EDG__ return _STD construct_at(_Location, _STD forward<_Types>(_Args)...); #else // ^^^ EDG / Other vvv - _MSVC_CONSTEXPR return ::new (static_cast(_Location)) _Ty(_STD forward<_Types>(_Args)...); + if constexpr (is_array_v<_Ty>) { + static_assert(sizeof...(_Types) == 0, "The array is only allowed to be value-initialized by " + "std::ranges::construct_at. (N5032 [specialized.construct]/2)"); +#if defined(__clang__) // TRANSITION, LLVM-117294 + ::new (static_cast(_Location)) _Ty(); + return __builtin_launder(_Location); // per old resolution of LWG-3436 +#elif defined(_MSC_VER) // TRANSITION, DevCom-10798069 + if constexpr (is_trivially_destructible_v<_Ty>) { + _MSVC_CONSTEXPR return ::new (_Secret_placement_new_tag{}, static_cast(_Location)) _Ty[1](); + } else { + // For non-trivially-destructible types, the workaround doesn't work + // because additional space is required to record the number of class objects to destroy. + return ::new (static_cast(_Location)) _Ty[1](); + } +#else // ^^^ workaround / no workaround vvv + _MSVC_CONSTEXPR return ::new (static_cast(_Location)) _Ty[1](); +#endif // ^^^ no workaround ^^^ + } else { + _MSVC_CONSTEXPR return ::new (static_cast(_Location)) _Ty(_STD forward<_Types>(_Args)...); + } #endif // ^^^ Other ^^^ } }; diff --git a/stl/inc/xutility b/stl/inc/xutility index f74264ac7c3..afa828543e4 100644 --- a/stl/inc/xutility +++ b/stl/inc/xutility @@ -249,6 +249,25 @@ __declspec(noalias) size_t __stdcall __std_mismatch_8(const void* _First1, const } // extern "C" +#if _HAS_CXX20 && !defined(__clang__) && !defined(__EDG__) // TRANSITION, DevCom-10798069 +_STD_BEGIN +struct _Secret_placement_new_tag { + explicit _Secret_placement_new_tag() = default; +}; +_STD_END + +template <_STD same_as<_STD _Secret_placement_new_tag> _Tag> +_NODISCARD _Ret_notnull_ _Post_writable_byte_size_(_Size) + _Post_satisfies_(return == _Where) constexpr void* __CRTDECL operator new[]( + size_t _Size, _Tag, _Writable_bytes_(_Size) void* _Where) noexcept { + (void) _Size; + return _Where; +} + +template <_STD same_as<_STD _Secret_placement_new_tag> _Tag> +constexpr void __CRTDECL operator delete[](void*, _Tag, void*) noexcept {} +#endif // ^^^ workaround ^^^ + _STD_BEGIN template @@ -590,12 +609,31 @@ struct _Get_rebind_alias<_Ty, _Other, void_t - requires requires(_Ty* _Location, _Types&&... _Args) { + requires (!is_unbounded_array_v<_Ty>) && requires(_Ty* _Location, _Types&&... _Args) { ::new (static_cast(_Location)) _Ty(_STD forward<_Types>(_Args)...); // per LWG-3888 } constexpr _Ty* construct_at(_Ty* const _Location, _Types&&... _Args) noexcept(noexcept(::new (static_cast(_Location)) _Ty(_STD forward<_Types>(_Args)...))) /* strengthened */ { - _MSVC_CONSTEXPR return ::new (static_cast(_Location)) _Ty(_STD forward<_Types>(_Args)...); + if constexpr (is_array_v<_Ty>) { + static_assert(sizeof...(_Types) == 0, "The array is only allowed to be value-initialized by std::construct_at. " + "(N5032 [specialized.construct]/2)"); +#if defined(__clang__) || defined(__EDG__) // TRANSITION, LLVM-117294, DevCom-10798145 + ::new (static_cast(_Location)) _Ty(); + return __builtin_launder(_Location); // per old resolution of LWG-3436 +#elif defined(_MSC_VER) // TRANSITION, DevCom-10798069 + if constexpr (is_trivially_destructible_v<_Ty>) { + _MSVC_CONSTEXPR return ::new (_Secret_placement_new_tag{}, static_cast(_Location)) _Ty[1](); + } else { + // For non-trivially-destructible types, the workaround doesn't work + // because additional space is required to record the number of class objects to destroy. + return ::new (static_cast(_Location)) _Ty[1](); + } +#else // ^^^ workaround / no workaround vvv + _MSVC_CONSTEXPR return ::new (static_cast(_Location)) _Ty[1](); +#endif // ^^^ no workaround ^^^ + } else { + _MSVC_CONSTEXPR return ::new (static_cast(_Location)) _Ty(_STD forward<_Types>(_Args)...); + } } #endif // _HAS_CXX20 diff --git a/tests/std/tests/P0784R7_library_support_for_more_constexpr_containers/test.cpp b/tests/std/tests/P0784R7_library_support_for_more_constexpr_containers/test.cpp index 8b5299caa1d..5dfd33f6bd9 100644 --- a/tests/std/tests/P0784R7_library_support_for_more_constexpr_containers/test.cpp +++ b/tests/std/tests/P0784R7_library_support_for_more_constexpr_containers/test.cpp @@ -67,6 +67,11 @@ static_assert(!can_construct_at); static_assert(!can_construct_at); static_assert(!can_construct_at); +static_assert(can_construct_at); +static_assert(can_construct_at); +static_assert(!can_construct_at); +static_assert(!can_construct_at); + struct X {}; static_assert(!can_construct_at); @@ -619,6 +624,72 @@ static_assert(!CanWellDefinedlyAccessAfterOperation<[](auto& arr) { ranges::dest static_assert(!CanWellDefinedlyAccessAfterOperation<[](auto& arr) { ranges::destroy_n(arr + 0, 1); }>); #endif // ^^^ no workaround ^^^ +// Test LWG-3436 "std::construct_at should support arrays" +template +constexpr void test_std_construct_at_array() { + union U { + constexpr U() {} + constexpr ~U() {} + + T a[N]; + }; + U u; + construct_at(&u.a); + for (const auto& elem : u.a) { + assert(elem == T{}); + } + destroy_at(&u.a); +} + +template +constexpr void test_ranges_construct_at_array() { + union U { + constexpr U() {} + constexpr ~U() {} + + T a[N]; + }; + U u; + ranges::construct_at(&u.a); + for (const auto& elem : u.a) { + assert(elem == T{}); + } + ranges::destroy_at(&u.a); +} + +constexpr bool test_construct_at_array() { + test_std_construct_at_array(); + test_std_construct_at_array(); + test_ranges_construct_at_array(); + test_ranges_construct_at_array(); + +#if !defined(__clang__) && !defined(__EDG__) // TRANSITION, DevCom-10798069 + if (!is_constant_evaluated()) +#endif // ^^^ workaround ^^^ + { +#if !_HAS_CXX23 + if (!is_constant_evaluated()) +#endif // !_HAS_CXX23 + { + test_std_construct_at_array, 1>(); + test_std_construct_at_array, 42>(); + test_ranges_construct_at_array, 1>(); + test_ranges_construct_at_array, 42>(); + } + test_std_construct_at_array(); + test_ranges_construct_at_array(); +#if defined(__EDG__) && _ITERATOR_DEBUG_LEVEL != 0 // TRANSITION, DevCom-11012299 + if (!is_constant_evaluated()) +#endif // ^^^ workaround ^^^ + { + test_std_construct_at_array(); + test_ranges_construct_at_array(); + } + } + + return true; +} + int main() { test_runtime(1234); test_runtime(string("hello world")); @@ -637,4 +708,7 @@ int main() { test_array(1234); test_array(string("hello world")); test_array(string("hello to some really long world that certainly doesn't fit in SSO")); + + test_construct_at_array(); + static_assert(test_construct_at_array()); } diff --git a/tests/std/tests/P1004R2_constexpr_vector/test.cpp b/tests/std/tests/P1004R2_constexpr_vector/test.cpp index a50b5383c62..c5e46cdcb24 100644 --- a/tests/std/tests/P1004R2_constexpr_vector/test.cpp +++ b/tests/std/tests/P1004R2_constexpr_vector/test.cpp @@ -657,11 +657,51 @@ constexpr bool test_growth() { return true; } +#pragma warning(push) +#pragma warning(disable : 4582) // '%s': constructor is not implicitly called +#pragma warning(disable : 4583) // '%s': destructor is not implicitly called +template +constexpr void test_vector_of_array_impl() { + vector v(42); + for (const auto& a : v) { + for (const auto& elem : a) { + assert(elem == T{}); + } + } +} + +constexpr bool test_vector_of_array() { + test_vector_of_array_impl(); + test_vector_of_array_impl(); + +#if !defined(__clang__) && !defined(__EDG__) // TRANSITION, DevCom-10798069 + if (!is_constant_evaluated()) +#endif // ^^^ workaround ^^^ + { +#if !_HAS_CXX23 + if (!is_constant_evaluated()) +#endif // !_HAS_CXX23 + { + test_vector_of_array_impl>, 1>(); + test_vector_of_array_impl>, 42>(); + } + test_vector_of_array_impl, 1>(); + test_vector_of_array_impl, 42>(); + } + + return true; +} +#pragma warning(pop) + int main() { test_interface(); test_iterators(); test_growth(); + test_vector_of_array(); static_assert(test_interface()); static_assert(test_iterators()); static_assert(test_growth()); +#ifndef __EDG__ // TRANSITION, DevCom-11008487 + static_assert(test_vector_of_array()); +#endif // ^^^ no workaround ^^^ } From a690f8442de28e1bd1c461ad27279c591954482f Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Mon, 2 Feb 2026 07:36:18 -0800 Subject: [PATCH 11/11] Update submodules: Boost.Math 1.90.0, Google Benchmark 1.9.5, LLVM (#6058) --- benchmarks/google-benchmark | 2 +- benchmarks/src/adjacent_difference.cpp | 2 +- benchmarks/src/adjacent_find.cpp | 2 +- benchmarks/src/find_and_count.cpp | 6 +++--- benchmarks/src/find_first_of.cpp | 2 +- benchmarks/src/includes.cpp | 2 +- benchmarks/src/iota.cpp | 2 +- benchmarks/src/is_sorted_until.cpp | 2 +- benchmarks/src/minmax_element.cpp | 2 +- benchmarks/src/mismatch.cpp | 2 +- benchmarks/src/priority_queue_push_range.cpp | 2 +- benchmarks/src/regex_match.cpp | 2 +- benchmarks/src/regex_search.cpp | 2 +- benchmarks/src/reverse.cpp | 2 +- benchmarks/src/rotate.cpp | 2 +- benchmarks/src/sample.cpp | 2 +- benchmarks/src/search.cpp | 2 +- benchmarks/src/search_n.cpp | 2 +- benchmarks/src/shuffle.cpp | 2 +- benchmarks/src/vector_bool_transform.cpp | 2 +- boost-math | 2 +- llvm-project | 2 +- tests/libcxx/expected_results.txt | 7 ++++--- 23 files changed, 28 insertions(+), 27 deletions(-) diff --git a/benchmarks/google-benchmark b/benchmarks/google-benchmark index eddb0241389..192ef10025e 160000 --- a/benchmarks/google-benchmark +++ b/benchmarks/google-benchmark @@ -1 +1 @@ -Subproject commit eddb0241389718a23a42db6af5f0164b6e0139af +Subproject commit 192ef10025eb2c4cdd392bc502f0c852196baa48 diff --git a/benchmarks/src/adjacent_difference.cpp b/benchmarks/src/adjacent_difference.cpp index 798bc813beb..9d38daf6cde 100644 --- a/benchmarks/src/adjacent_difference.cpp +++ b/benchmarks/src/adjacent_difference.cpp @@ -40,7 +40,7 @@ void bm(benchmark::State& state) { } } -void common_args(auto bm) { +void common_args(benchmark::Benchmark* bm) { bm->Arg(2255); } diff --git a/benchmarks/src/adjacent_find.cpp b/benchmarks/src/adjacent_find.cpp index 1e1aee08bb4..4d436d35f9f 100644 --- a/benchmarks/src/adjacent_find.cpp +++ b/benchmarks/src/adjacent_find.cpp @@ -41,7 +41,7 @@ void bm(benchmark::State& state) { } } -void common_args(auto bm) { +void common_args(benchmark::Benchmark* bm) { bm->ArgPair(2525, 1142); } diff --git a/benchmarks/src/find_and_count.cpp b/benchmarks/src/find_and_count.cpp index d17fd0e6df2..e4735adf029 100644 --- a/benchmarks/src/find_and_count.cpp +++ b/benchmarks/src/find_and_count.cpp @@ -66,15 +66,15 @@ void bm(benchmark::State& state) { } } -void common_args(auto bm) { +void common_args(benchmark::Benchmark* bm) { bm->Args({8021, 3056}); // AVX tail tests bm->Args({63, 62})->Args({31, 30})->Args({15, 14})->Args({7, 6}); } struct point { - int16_t x; - int16_t y; + int16_t x{}; + int16_t y{}; bool operator==(const point&) const = default; }; diff --git a/benchmarks/src/find_first_of.cpp b/benchmarks/src/find_first_of.cpp index bf6a8585f02..649c7934713 100644 --- a/benchmarks/src/find_first_of.cpp +++ b/benchmarks/src/find_first_of.cpp @@ -81,7 +81,7 @@ void bm(benchmark::State& state) { } } -void common_args(auto bm) { +void common_args(benchmark::Benchmark* bm) { bm->Args({2, 3})->Args({6, 81})->Args({7, 4})->Args({9, 3})->Args({22, 5})->Args({58, 2}); bm->Args({75, 85})->Args({102, 4})->Args({200, 46})->Args({325, 1})->Args({400, 50}); bm->Args({1011, 11})->Args({1280, 46})->Args({1502, 23})->Args({2203, 54})->Args({3056, 7}); diff --git a/benchmarks/src/includes.cpp b/benchmarks/src/includes.cpp index 2a54ff4a1da..3967af2d212 100644 --- a/benchmarks/src/includes.cpp +++ b/benchmarks/src/includes.cpp @@ -96,7 +96,7 @@ void bm_includes(benchmark::State& state) { } } -void common_args(auto bm) { +void common_args(benchmark::Benchmark* bm) { for (const auto& spread : {needle_spread::dense, needle_spread::dense_random, needle_spread::sparse, needle_spread::sparse_random}) { for (const auto& expected_match : {true, false}) { diff --git a/benchmarks/src/iota.cpp b/benchmarks/src/iota.cpp index d1bd8b97807..0cf9162bc8d 100644 --- a/benchmarks/src/iota.cpp +++ b/benchmarks/src/iota.cpp @@ -31,7 +31,7 @@ void bm(benchmark::State& state) { } } -void common_args(auto bm) { +void common_args(benchmark::Benchmark* bm) { bm->Arg(7)->Arg(18)->Arg(43)->Arg(131)->Arg(315)->Arg(1212); } diff --git a/benchmarks/src/is_sorted_until.cpp b/benchmarks/src/is_sorted_until.cpp index a70cc01b487..66b60d897d6 100644 --- a/benchmarks/src/is_sorted_until.cpp +++ b/benchmarks/src/is_sorted_until.cpp @@ -43,7 +43,7 @@ void bm_is_sorted_until(benchmark::State& state) { } } -void common_args(auto bm) { +void common_args(benchmark::Benchmark* bm) { bm->ArgPair(3000, 1800); } diff --git a/benchmarks/src/minmax_element.cpp b/benchmarks/src/minmax_element.cpp index 8a649502c3f..3ade85d7ea8 100644 --- a/benchmarks/src/minmax_element.cpp +++ b/benchmarks/src/minmax_element.cpp @@ -57,7 +57,7 @@ void bm(benchmark::State& state) { } template -void common_arg(auto bm) { +void common_arg(benchmark::Benchmark* bm) { bm->Arg(8021); // AVX tail tests bm->Arg(63 / ElementSize); diff --git a/benchmarks/src/mismatch.cpp b/benchmarks/src/mismatch.cpp index 9d4c79f5c7b..0fbcd587fa1 100644 --- a/benchmarks/src/mismatch.cpp +++ b/benchmarks/src/mismatch.cpp @@ -48,7 +48,7 @@ void bm(benchmark::State& state) { } } -void common_args(auto bm) { +void common_args(benchmark::Benchmark* bm) { bm->Args({8, 3})->Args({24, 22})->Args({105, -1})->Args({4021, 3056}); } diff --git a/benchmarks/src/priority_queue_push_range.cpp b/benchmarks/src/priority_queue_push_range.cpp index d2e96bf17a9..c8d14957f80 100644 --- a/benchmarks/src/priority_queue_push_range.cpp +++ b/benchmarks/src/priority_queue_push_range.cpp @@ -56,7 +56,7 @@ void BM_push_range(benchmark::State& state) { } } -void common_args(auto bm) { +void common_args(benchmark::Benchmark* bm) { bm->RangeMultiplier(100)->Range(1, vec_size)->Arg(vec_size / 2 + 1); } diff --git a/benchmarks/src/regex_match.cpp b/benchmarks/src/regex_match.cpp index e705bde94b4..56fa702721e 100644 --- a/benchmarks/src/regex_match.cpp +++ b/benchmarks/src/regex_match.cpp @@ -39,7 +39,7 @@ void bm_match_sequence_of_9a1b(benchmark::State& state, const char* pattern, syn } } -void common_args(auto bm) { +void common_args(benchmark::Benchmark* bm) { bm->Arg(100)->Arg(200)->Arg(400); } diff --git a/benchmarks/src/regex_search.cpp b/benchmarks/src/regex_search.cpp index aeb46d88b26..9fd341db64e 100644 --- a/benchmarks/src/regex_search.cpp +++ b/benchmarks/src/regex_search.cpp @@ -32,7 +32,7 @@ void bm_lorem_search(benchmark::State& state, const char* pattern, syntax_option } } -void common_args(auto bm) { +void common_args(benchmark::Benchmark* bm) { bm->Arg(2)->Arg(3)->Arg(4); } diff --git a/benchmarks/src/reverse.cpp b/benchmarks/src/reverse.cpp index b4566a9aaa1..b0e0e6cefe0 100644 --- a/benchmarks/src/reverse.cpp +++ b/benchmarks/src/reverse.cpp @@ -33,7 +33,7 @@ void rc(benchmark::State& state) { } } -void common_args(auto bm) { +void common_args(benchmark::Benchmark* bm) { bm->Arg(3449); // AVX tail tests bm->Arg(63)->Arg(31)->Arg(15)->Arg(7); diff --git a/benchmarks/src/rotate.cpp b/benchmarks/src/rotate.cpp index c0780aaf6e7..8852956b47e 100644 --- a/benchmarks/src/rotate.cpp +++ b/benchmarks/src/rotate.cpp @@ -31,7 +31,7 @@ void bm_rotate(benchmark::State& state) { } } -void common_args(auto bm) { +void common_args(benchmark::Benchmark* bm) { bm->Args({3333, 2242})->Args({3332, 1666})->Args({3333, 1111})->Args({3333, 501}); bm->Args({3333, 3300})->Args({3333, 12})->Args({3333, 5})->Args({3333, 1}); bm->Args({333, 101})->Args({123, 32})->Args({23, 7})->Args({12, 5})->Args({3, 2}); diff --git a/benchmarks/src/sample.cpp b/benchmarks/src/sample.cpp index e27776f2420..c4afd351daf 100644 --- a/benchmarks/src/sample.cpp +++ b/benchmarks/src/sample.cpp @@ -36,7 +36,7 @@ void bm_sample(benchmark::State& state) { } } -void common_args(auto bm) { +void common_args(benchmark::Benchmark* bm) { bm->Args({1 << 20, 1 << 15}); } diff --git a/benchmarks/src/search.cpp b/benchmarks/src/search.cpp index 4f803cec3a2..fe4296b9398 100644 --- a/benchmarks/src/search.cpp +++ b/benchmarks/src/search.cpp @@ -184,7 +184,7 @@ void member_rfind(benchmark::State& state) { } } -void common_args(auto bm) { +void common_args(benchmark::Benchmark* bm) { bm->DenseRange(0, std::size(patterns) - 1, 1); } diff --git a/benchmarks/src/search_n.cpp b/benchmarks/src/search_n.cpp index c20bb673875..ee65c6d79a5 100644 --- a/benchmarks/src/search_n.cpp +++ b/benchmarks/src/search_n.cpp @@ -61,7 +61,7 @@ void bm(benchmark::State& state) { } } -void common_args(auto bm) { +void common_args(benchmark::Benchmark* bm) { for (const auto& n : {40, 18, 16, 14, 10, 8, 5, 4, 3, 2, 1}) { bm->ArgPair(3000, n); } diff --git a/benchmarks/src/shuffle.cpp b/benchmarks/src/shuffle.cpp index b87f7f9ee89..97fee454e42 100644 --- a/benchmarks/src/shuffle.cpp +++ b/benchmarks/src/shuffle.cpp @@ -32,7 +32,7 @@ void bm_shuffle(benchmark::State& state) { } } -void common_args(auto bm) { +void common_args(benchmark::Benchmark* bm) { bm->Arg(1 << 20); } diff --git a/benchmarks/src/vector_bool_transform.cpp b/benchmarks/src/vector_bool_transform.cpp index 4f54882c18c..25e571b499c 100644 --- a/benchmarks/src/vector_bool_transform.cpp +++ b/benchmarks/src/vector_bool_transform.cpp @@ -42,7 +42,7 @@ void transform_two_inputs_aligned(benchmark::State& state) { } } -void common_args(auto bm) { +void common_args(benchmark::Benchmark* bm) { bm->RangeMultiplier(64)->Range(64, 64 << 10); } diff --git a/boost-math b/boost-math index 5e088ffe2ed..e0fcd19f722 160000 --- a/boost-math +++ b/boost-math @@ -1 +1 @@ -Subproject commit 5e088ffe2ed0e237b9069e3a7352865283d8f196 +Subproject commit e0fcd19f7227d81391770ea46015acc3c80af810 diff --git a/llvm-project b/llvm-project index 5d501d1a8d2..3bf2c8347e0 160000 --- a/llvm-project +++ b/llvm-project @@ -1 +1 @@ -Subproject commit 5d501d1a8d287b9c410aa46ffb5534859e518a8a +Subproject commit 3bf2c8347e04ba48dad1a7d73c5dd3b5e6dc8b7c diff --git a/tests/libcxx/expected_results.txt b/tests/libcxx/expected_results.txt index 75d9b248ee3..a86a113444f 100644 --- a/tests/libcxx/expected_results.txt +++ b/tests/libcxx/expected_results.txt @@ -67,6 +67,9 @@ std/language.support/support.coroutines/coroutine.handle/coroutine.handle.noop/n std/concepts/concepts.compare/concept.equalitycomparable/equality_comparable_with.compile.pass.cpp:2 FAIL std/language.support/cmp/cmp.concept/three_way_comparable_with.compile.pass.cpp:2 FAIL +# LLVM-178855: [libc++][test] -Wunused-variable warning in thread.semaphore/lost_wakeup.timed.pass.cpp +std/thread/thread.semaphore/lost_wakeup.timed.pass.cpp:2 FAIL + # Non-Standard regex behavior. # "It seems likely that the test is still non-conforming due to how libc++ handles the 'w' character class." std/re/re.traits/lookup_classname.pass.cpp FAIL @@ -1209,9 +1212,6 @@ std/algorithms/algorithms.results/min_max_result.pass.cpp:1 FAIL # Not analyzed. std/algorithms/robust_against_proxy_iterators_lifetime_bugs.pass.cpp FAIL -# Not analyzed. Inspecting shift operators for quoted(). -std/input.output/iostream.format/quoted.manip/quoted_traits.compile.pass.cpp FAIL - # Not analyzed. Failing assert(arr[0].moves() == 1 && arr[1].moves() == 3). std/iterators/iterator.requirements/iterator.cust/iterator.cust.swap/iter_swap.pass.cpp FAIL @@ -1255,6 +1255,7 @@ std/strings/basic.string/string.modifiers/string_replace/replace_with_range.pass std/utilities/charconv/charconv.to.chars/integral.pass.cpp FAIL # Not analyzed, failing due to constexpr step limits. SKIPPED because they occasionally pass in certain configurations. +std/algorithms/alg.modifying.operations/alg.shift/ranges.shift_left.pass.cpp SKIPPED std/utilities/template.bitset/bitset.members/left_shift_eq.pass.cpp SKIPPED std/utilities/template.bitset/bitset.members/op_and_eq.pass.cpp SKIPPED std/utilities/template.bitset/bitset.members/op_or_eq.pass.cpp SKIPPED