diff --git a/stl/inc/mutex b/stl/inc/mutex index b133a8c1ce..dbfdff196f 100644 --- a/stl/inc/mutex +++ b/stl/inc/mutex @@ -30,20 +30,37 @@ _STD_BEGIN _EXPORT_STD class condition_variable; _EXPORT_STD class condition_variable_any; +struct _Mtx_internal_imp_mirror { +#ifdef _CRT_WINDOWS +#ifdef _WIN64 + static constexpr size_t _Critical_section_size = 16; +#else // _WIN64 + static constexpr size_t _Critical_section_size = 8; +#endif // _WIN64 +#else // _CRT_WINDOWS +#ifdef _WIN64 + static constexpr size_t _Critical_section_size = 64; +#else // _WIN64 + static constexpr size_t _Critical_section_size = 36; +#endif // _WIN64 +#endif // _CRT_WINDOWS + + static constexpr size_t _Critical_section_align = alignof(void*); + + int _Type; + _Aligned_storage_t<_Critical_section_size, _Critical_section_align> _Cs_storage; + long _Thread_id; + int _Count; +}; + +static_assert(sizeof(_Mtx_internal_imp_mirror) == _Mtx_internal_imp_size, "inconsistent size for mutex"); +static_assert(alignof(_Mtx_internal_imp_mirror) == _Mtx_internal_imp_alignment, "inconsistent alignment for mutex"); + class _Mutex_base { // base class for all mutex types public: -#ifdef _DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR _Mutex_base(int _Flags = 0) noexcept { _Mtx_init_in_situ(_Mymtx(), _Flags | _Mtx_try); } -#else // ^^^ _DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR / !_DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR vvv - constexpr _Mutex_base(int _Flags = 0) noexcept { - _Mtx_storage._Critical_section = {}; - _Mtx_storage._Thread_id = -1; - _Mtx_storage._Type = _Flags | _Mtx_try; - _Mtx_storage._Count = 0; - } -#endif // !_DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR ~_Mutex_base() noexcept { _Mtx_destroy_in_situ(_Mymtx()); @@ -79,9 +96,9 @@ public: protected: _NODISCARD_TRY_CHANGE_STATE bool _Verify_ownership_levels() noexcept { - if (_Mtx_storage._Count == INT_MAX) { + if (_Mtx_storage_mirror._Count == INT_MAX) { // only occurs for recursive mutexes (N4950 [thread.mutex.recursive]/3) - --_Mtx_storage._Count; + --_Mtx_storage_mirror._Count; return false; } @@ -92,16 +109,23 @@ private: friend condition_variable; friend condition_variable_any; - _Mtx_internal_imp_t _Mtx_storage{}; + union { + _Aligned_storage_t<_Mtx_internal_imp_size, _Mtx_internal_imp_alignment> _Mtx_storage; + _Mtx_internal_imp_mirror _Mtx_storage_mirror; + }; - _Mtx_t _Mymtx() noexcept { - return &_Mtx_storage; + _Mtx_t _Mymtx() noexcept { // get pointer to _Mtx_internal_imp_t inside _Mtx_storage + return reinterpret_cast<_Mtx_t>(&_Mtx_storage); } }; +static_assert(sizeof(_Mutex_base) == _Mtx_internal_imp_size, "inconsistent size for mutex"); +static_assert(alignof(_Mutex_base) == _Mtx_internal_imp_alignment, "inconsistent alignment for mutex"); + _EXPORT_STD class mutex : public _Mutex_base { // class for mutual exclusion public: - mutex() noexcept = default; + /* constexpr */ mutex() noexcept // TRANSITION, ABI + : _Mutex_base() {} mutex(const mutex&) = delete; mutex& operator=(const mutex&) = delete; diff --git a/stl/inc/xthreads.h b/stl/inc/xthreads.h index 26302f0aea..b2c4beb4bb 100644 --- a/stl/inc/xthreads.h +++ b/stl/inc/xthreads.h @@ -8,7 +8,6 @@ #include #if _STL_COMPILER_PREPROCESSOR #include -#include #include #pragma pack(push, _CRT_PACKING) @@ -25,56 +24,40 @@ struct _Thrd_t { // thread identifier for Win32 _Thrd_id_t _Id; }; -using _Smtx_t = void*; - -struct _Stl_critical_section { - void* _Unused = nullptr; // TRANSITION, ABI: was the vptr - _Smtx_t _M_srw_lock = nullptr; -}; - -struct _Mtx_internal_imp_t { +// Size and alignment for _Mtx_internal_imp_t and _Cnd_internal_imp_t #ifdef _CRT_WINDOWS #ifdef _WIN64 - static constexpr size_t _Critical_section_size = 16; -#else // ^^^ defined(_WIN64) / !defined(_WIN64) vvv - static constexpr size_t _Critical_section_size = 8; -#endif // ^^^ !defined(_WIN64) ^^^ -#else // ^^^ defined(_CRT_WINDOWS) / !defined(_CRT_WINDOWS) vvv +_INLINE_VAR constexpr size_t _Mtx_internal_imp_size = 32; +_INLINE_VAR constexpr size_t _Mtx_internal_imp_alignment = 8; +_INLINE_VAR constexpr size_t _Cnd_internal_imp_size = 16; +_INLINE_VAR constexpr size_t _Cnd_internal_imp_alignment = 8; +#else // _WIN64 +_INLINE_VAR constexpr size_t _Mtx_internal_imp_size = 20; +_INLINE_VAR constexpr size_t _Mtx_internal_imp_alignment = 4; +_INLINE_VAR constexpr size_t _Cnd_internal_imp_size = 8; +_INLINE_VAR constexpr size_t _Cnd_internal_imp_alignment = 4; +#endif // _WIN64 +#else // _CRT_WINDOWS #ifdef _WIN64 - static constexpr size_t _Critical_section_size = 64; -#else // ^^^ defined(_WIN64) / !defined(_WIN64) vvv - static constexpr size_t _Critical_section_size = 36; -#endif // ^^^ !defined(_WIN64) ^^^ -#endif // ^^^ !defined(_CRT_WINDOWS) ^^^ - - static constexpr size_t _Critical_section_align = alignof(void*); - - int _Type{}; - union { - _Stl_critical_section _Critical_section{}; - _STD _Aligned_storage_t<_Critical_section_size, _Critical_section_align> _Cs_storage; - }; - long _Thread_id{}; - int _Count{}; -}; - -// Size and alignment for _Cnd_internal_imp_t -#if defined(_CRT_WINDOWS) // for Windows-internal code -_INLINE_VAR constexpr size_t _Cnd_internal_imp_size = 2 * sizeof(void*); -#elif defined(_WIN64) // ordinary 64-bit code -_INLINE_VAR constexpr size_t _Cnd_internal_imp_size = 72; -#else // vvv ordinary 32-bit code vvv -_INLINE_VAR constexpr size_t _Cnd_internal_imp_size = 40; -#endif // ^^^ ordinary 32-bit code ^^^ - -_INLINE_VAR constexpr size_t _Cnd_internal_imp_alignment = alignof(void*); - -using _Mtx_t = _Mtx_internal_imp_t*; - -#ifdef _M_CEE // avoid warning LNK4248: unresolved typeref token for '_Cnd_internal_imp_t'; image may not run +_INLINE_VAR constexpr size_t _Mtx_internal_imp_size = 80; +_INLINE_VAR constexpr size_t _Mtx_internal_imp_alignment = 8; +_INLINE_VAR constexpr size_t _Cnd_internal_imp_size = 72; +_INLINE_VAR constexpr size_t _Cnd_internal_imp_alignment = 8; +#else // _WIN64 +_INLINE_VAR constexpr size_t _Mtx_internal_imp_size = 48; +_INLINE_VAR constexpr size_t _Mtx_internal_imp_alignment = 4; +_INLINE_VAR constexpr size_t _Cnd_internal_imp_size = 40; +_INLINE_VAR constexpr size_t _Cnd_internal_imp_alignment = 4; +#endif // _WIN64 +#endif // _CRT_WINDOWS + +#ifdef _M_CEE // avoid warning LNK4248: unresolved typeref token for '_Mtx_internal_imp_t'; image may not run +using _Mtx_t = void*; using _Cnd_t = void*; #else // ^^^ defined(_M_CEE) / !defined(_M_CEE) vvv +struct _Mtx_internal_imp_t; struct _Cnd_internal_imp_t; +using _Mtx_t = _Mtx_internal_imp_t*; using _Cnd_t = _Cnd_internal_imp_t*; #endif // ^^^ !defined(_M_CEE) ^^^ @@ -103,6 +86,7 @@ _CRTIMP2_PURE void __cdecl _Mtx_destroy(_Mtx_t); _CRTIMP2_PURE void __cdecl _Mtx_init_in_situ(_Mtx_t, int); _CRTIMP2_PURE void __cdecl _Mtx_destroy_in_situ(_Mtx_t); _CRTIMP2_PURE int __cdecl _Mtx_current_owns(_Mtx_t); +_CRTIMP2_PURE void* __cdecl _Mtx_getconcrtcs(_Mtx_t); _CRTIMP2_PURE _Thrd_result __cdecl _Mtx_lock(_Mtx_t); _CRTIMP2_PURE _Thrd_result __cdecl _Mtx_trylock(_Mtx_t); _CRTIMP2_PURE _Thrd_result __cdecl _Mtx_unlock(_Mtx_t); // TRANSITION, ABI: Always succeeds @@ -114,6 +98,7 @@ _CRTIMP2_PURE void __cdecl _Mtx_reset_owner(_Mtx_t); // shared mutex // these declarations must be in sync with those in sharedmutex.cpp +using _Smtx_t = void*; void __cdecl _Smtx_lock_exclusive(_Smtx_t*); void __cdecl _Smtx_lock_shared(_Smtx_t*); int __cdecl _Smtx_try_lock_exclusive(_Smtx_t*); diff --git a/stl/src/cond.cpp b/stl/src/cond.cpp index 01d964ef6f..4f9bb2a0cb 100644 --- a/stl/src/cond.cpp +++ b/stl/src/cond.cpp @@ -49,7 +49,7 @@ _CRTIMP2_PURE void __cdecl _Cnd_destroy(const _Cnd_t cond) { // clean up } _CRTIMP2_PURE _Thrd_result __cdecl _Cnd_wait(const _Cnd_t cond, const _Mtx_t mtx) { // wait until signaled - const auto cs = &mtx->_Critical_section; + const auto cs = static_cast(_Mtx_getconcrtcs(mtx)); _Mtx_clear_owner(mtx); cond->_get_cv()->wait(cs); _Mtx_reset_owner(mtx); @@ -60,7 +60,7 @@ _CRTIMP2_PURE _Thrd_result __cdecl _Cnd_wait(const _Cnd_t cond, const _Mtx_t mtx _CRTIMP2_PURE _Thrd_result __cdecl _Cnd_timedwait( const _Cnd_t cond, const _Mtx_t mtx, const _timespec64* const target) { _Thrd_result res = _Thrd_result::_Success; - const auto cs = &mtx->_Critical_section; + const auto cs = static_cast(_Mtx_getconcrtcs(mtx)); if (target == nullptr) { // no target time specified, wait on mutex _Mtx_clear_owner(mtx); cond->_get_cv()->wait(cs); diff --git a/stl/src/mutex.cpp b/stl/src/mutex.cpp index ba8c2de344..eaf869e585 100644 --- a/stl/src/mutex.cpp +++ b/stl/src/mutex.cpp @@ -33,20 +33,35 @@ _EXTERN_C enum class __stl_sync_api_modes_enum { normal, win7, vista, concrt }; _CRTIMP2 void __cdecl __set_stl_sync_api_mode(__stl_sync_api_modes_enum) {} -[[nodiscard]] static PSRWLOCK get_srw_lock(_Mtx_t mtx) { - return reinterpret_cast(&mtx->_Critical_section._M_srw_lock); -} +struct _Mtx_internal_imp_t { // ConcRT mutex + int type; + typename std::_Aligned_storage::type cs; + long thread_id; + int count; + [[nodiscard]] Concurrency::details::stl_critical_section_win7* _get_cs() { // get pointer to implementation + return reinterpret_cast(&cs); + } +}; + +static_assert(sizeof(_Mtx_internal_imp_t) == _Mtx_internal_imp_size, "incorrect _Mtx_internal_imp_size"); +static_assert(alignof(_Mtx_internal_imp_t) == _Mtx_internal_imp_alignment, "incorrect _Mtx_internal_imp_alignment"); + +static_assert( + std::_Mtx_internal_imp_mirror::_Critical_section_size == Concurrency::details::stl_critical_section_max_size); +static_assert( + std::_Mtx_internal_imp_mirror::_Critical_section_align == Concurrency::details::stl_critical_section_max_alignment); // TRANSITION, only used when constexpr mutex constructor is not enabled _CRTIMP2_PURE void __cdecl _Mtx_init_in_situ(_Mtx_t mtx, int type) { // initialize mutex in situ - Concurrency::details::create_stl_critical_section(&mtx->_Critical_section); - mtx->_Thread_id = -1; - mtx->_Type = type; - mtx->_Count = 0; + Concurrency::details::create_stl_critical_section(mtx->_get_cs()); + mtx->thread_id = -1; + mtx->type = type; + mtx->count = 0; } _CRTIMP2_PURE void __cdecl _Mtx_destroy_in_situ(_Mtx_t mtx) { // destroy mutex in situ - _THREAD_ASSERT(mtx->_Count == 0, "mutex destroyed while busy"); + _THREAD_ASSERT(mtx->count == 0, "mutex destroyed while busy"); (void) mtx; } @@ -75,29 +90,29 @@ _CRTIMP2_PURE void __cdecl _Mtx_destroy(_Mtx_t mtx) { // destroy mutex static _Thrd_result mtx_do_lock(_Mtx_t mtx, const _timespec64* target) { // lock mutex // TRANSITION, ABI: the use of `const _timespec64*` is preserved for `_Mtx_timedlock` const auto current_thread_id = static_cast(GetCurrentThreadId()); - if ((mtx->_Type & ~_Mtx_recursive) == _Mtx_plain) { // set the lock + if ((mtx->type & ~_Mtx_recursive) == _Mtx_plain) { // set the lock // TRANSITION, ABI: this branch is preserved for `_Thrd_create` - if (mtx->_Thread_id != current_thread_id) { // not current thread, do lock - AcquireSRWLockExclusive(get_srw_lock(mtx)); - mtx->_Thread_id = current_thread_id; + if (mtx->thread_id != current_thread_id) { // not current thread, do lock + mtx->_get_cs()->lock(); + mtx->thread_id = current_thread_id; } - ++mtx->_Count; + ++mtx->count; return _Thrd_result::_Success; } else { // handle timed or recursive mutex int res = WAIT_TIMEOUT; if (target == nullptr) { // no target --> plain wait (i.e. infinite timeout) - if (mtx->_Thread_id != current_thread_id) { - AcquireSRWLockExclusive(get_srw_lock(mtx)); + if (mtx->thread_id != current_thread_id) { + mtx->_get_cs()->lock(); } res = WAIT_OBJECT_0; } else if (target->tv_sec < 0 || target->tv_sec == 0 && target->tv_nsec <= 0) { // target time <= 0 --> plain trylock or timed wait for time that has passed; try to lock with 0 timeout - if (mtx->_Thread_id != current_thread_id) { // not this thread, lock it - if (TryAcquireSRWLockExclusive(get_srw_lock(mtx)) != 0) { + if (mtx->thread_id != current_thread_id) { // not this thread, lock it + if (mtx->_get_cs()->try_lock()) { res = WAIT_OBJECT_0; } } else { @@ -110,8 +125,7 @@ static _Thrd_result mtx_do_lock(_Mtx_t mtx, const _timespec64* target) { // lock _Timespec64_get_sys(&now); while (now.tv_sec < target->tv_sec || now.tv_sec == target->tv_sec && now.tv_nsec < target->tv_nsec) { // time has not expired - if (mtx->_Thread_id == current_thread_id - || TryAcquireSRWLockExclusive(get_srw_lock(mtx)) != 0) { // stop waiting + if (mtx->thread_id == current_thread_id || mtx->_get_cs()->try_lock()) { // stop waiting res = WAIT_OBJECT_0; break; } @@ -121,13 +135,13 @@ static _Thrd_result mtx_do_lock(_Mtx_t mtx, const _timespec64* target) { // lock } if (res == WAIT_OBJECT_0) { - if (1 < ++mtx->_Count) { // check count - if ((mtx->_Type & _Mtx_recursive) != _Mtx_recursive) { // not recursive, fixup count - --mtx->_Count; + if (1 < ++mtx->count) { // check count + if ((mtx->type & _Mtx_recursive) != _Mtx_recursive) { // not recursive, fixup count + --mtx->count; res = WAIT_TIMEOUT; } } else { - mtx->_Thread_id = current_thread_id; + mtx->thread_id = current_thread_id; } } @@ -147,14 +161,11 @@ static _Thrd_result mtx_do_lock(_Mtx_t mtx, const _timespec64* target) { // lock _CRTIMP2_PURE _Thrd_result __cdecl _Mtx_unlock(_Mtx_t mtx) { // unlock mutex _THREAD_ASSERT( - 1 <= mtx->_Count && mtx->_Thread_id == static_cast(GetCurrentThreadId()), "unlock of unowned mutex"); - - if (--mtx->_Count == 0) { // leave critical section - mtx->_Thread_id = -1; + 1 <= mtx->count && mtx->thread_id == static_cast(GetCurrentThreadId()), "unlock of unowned mutex"); - auto srw_lock = get_srw_lock(mtx); - _Analysis_assume_lock_held_(*srw_lock); - ReleaseSRWLockExclusive(srw_lock); + if (--mtx->count == 0) { // leave critical section + mtx->thread_id = -1; + mtx->_get_cs()->unlock(); } return _Thrd_result::_Success; // TRANSITION, ABI: Always succeeds } @@ -165,7 +176,7 @@ _CRTIMP2_PURE _Thrd_result __cdecl _Mtx_lock(_Mtx_t mtx) { // lock mutex _CRTIMP2_PURE _Thrd_result __cdecl _Mtx_trylock(_Mtx_t mtx) { // attempt to lock try_mutex _timespec64 xt; - _THREAD_ASSERT((mtx->_Type & (_Mtx_try | _Mtx_timed)) != 0, "trylock not supported by mutex"); + _THREAD_ASSERT((mtx->type & (_Mtx_try | _Mtx_timed)) != 0, "trylock not supported by mutex"); xt.tv_sec = 0; xt.tv_nsec = 0; return mtx_do_lock(mtx, &xt); @@ -175,28 +186,27 @@ _CRTIMP2_PURE _Thrd_result __cdecl _Mtx_trylock(_Mtx_t mtx) { // attempt to lock _CRTIMP2_PURE _Thrd_result __cdecl _Mtx_timedlock(_Mtx_t mtx, const _timespec64* xt) { // attempt to lock timed mutex _Thrd_result res; - _THREAD_ASSERT((mtx->_Type & _Mtx_timed) != 0, "timedlock not supported by mutex"); + _THREAD_ASSERT((mtx->type & _Mtx_timed) != 0, "timedlock not supported by mutex"); res = mtx_do_lock(mtx, xt); return res == _Thrd_result::_Busy ? _Thrd_result::_Timedout : res; } _CRTIMP2_PURE int __cdecl _Mtx_current_owns(_Mtx_t mtx) { // test if current thread owns mutex - return mtx->_Count != 0 && mtx->_Thread_id == static_cast(GetCurrentThreadId()); + return mtx->count != 0 && mtx->thread_id == static_cast(GetCurrentThreadId()); } -// TRANSITION, ABI: preserved for binary compatibility _CRTIMP2_PURE void* __cdecl _Mtx_getconcrtcs(_Mtx_t mtx) { // get internal cs impl - return &mtx->_Critical_section; + return mtx->_get_cs(); } _CRTIMP2_PURE void __cdecl _Mtx_clear_owner(_Mtx_t mtx) { // set owner to nobody - mtx->_Thread_id = -1; - --mtx->_Count; + mtx->thread_id = -1; + --mtx->count; } _CRTIMP2_PURE void __cdecl _Mtx_reset_owner(_Mtx_t mtx) { // set owner to current thread - mtx->_Thread_id = static_cast(GetCurrentThreadId()); - ++mtx->_Count; + mtx->thread_id = static_cast(GetCurrentThreadId()); + ++mtx->count; } _END_EXTERN_C diff --git a/stl/src/primitives.hpp b/stl/src/primitives.hpp index 89d35a2c7d..62684e6ec8 100644 --- a/stl/src/primitives.hpp +++ b/stl/src/primitives.hpp @@ -10,6 +10,36 @@ namespace Concurrency { namespace details { + class stl_critical_section_win7 { + public: + stl_critical_section_win7() = default; + + ~stl_critical_section_win7() = delete; + stl_critical_section_win7(const stl_critical_section_win7&) = delete; + stl_critical_section_win7& operator=(const stl_critical_section_win7&) = delete; + + void lock() { + AcquireSRWLockExclusive(&m_srw_lock); + } + + bool try_lock() { + return TryAcquireSRWLockExclusive(&m_srw_lock) != 0; + } + + void unlock() { + _Analysis_assume_lock_held_(m_srw_lock); + ReleaseSRWLockExclusive(&m_srw_lock); + } + + PSRWLOCK native_handle() { + return &m_srw_lock; + } + + private: + void* unused = nullptr; // TRANSITION, ABI: was the vptr + SRWLOCK m_srw_lock = SRWLOCK_INIT; + }; + class stl_condition_variable_win7 { public: stl_condition_variable_win7() = default; @@ -18,16 +48,14 @@ namespace Concurrency { stl_condition_variable_win7(const stl_condition_variable_win7&) = delete; stl_condition_variable_win7& operator=(const stl_condition_variable_win7&) = delete; - void wait(_Stl_critical_section* lock) { + void wait(stl_critical_section_win7* lock) { if (!wait_for(lock, INFINITE)) { _CSTD abort(); } } - bool wait_for(_Stl_critical_section* lock, unsigned int timeout) { - return SleepConditionVariableSRW( - &m_condition_variable, reinterpret_cast(&lock->_M_srw_lock), timeout, 0) - != 0; + bool wait_for(stl_critical_section_win7* lock, unsigned int timeout) { + return SleepConditionVariableSRW(&m_condition_variable, lock->native_handle(), timeout, 0) != 0; } void notify_one() { @@ -43,13 +71,22 @@ namespace Concurrency { CONDITION_VARIABLE m_condition_variable = CONDITION_VARIABLE_INIT; }; - // TRANSITION, only used when constexpr mutex constructor is not enabled - inline void create_stl_critical_section(_Stl_critical_section* p) { - new (p) _Stl_critical_section; + inline void create_stl_critical_section(stl_critical_section_win7* p) { + new (p) stl_critical_section_win7; } inline void create_stl_condition_variable(stl_condition_variable_win7* p) { new (p) stl_condition_variable_win7; } + +#if defined(_CRT_WINDOWS) // for Windows-internal code + const size_t stl_critical_section_max_size = 2 * sizeof(void*); +#elif defined(_WIN64) // ordinary 64-bit code + const size_t stl_critical_section_max_size = 64; +#else // vvv ordinary 32-bit code vvv + const size_t stl_critical_section_max_size = 36; +#endif // ^^^ ordinary 32-bit code ^^^ + + const size_t stl_critical_section_max_alignment = alignof(void*); } // namespace details } // namespace Concurrency diff --git a/tests/std/tests/VSO_0226079_mutex/env.lst b/tests/std/tests/VSO_0226079_mutex/env.lst index 371ea99eca..f141421b29 100644 --- a/tests/std/tests/VSO_0226079_mutex/env.lst +++ b/tests/std/tests/VSO_0226079_mutex/env.lst @@ -2,6 +2,3 @@ # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception RUNALL_INCLUDE ..\impure_matrix.lst -RUNALL_CROSSLIST -PM_CL="/D_DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR" -PM_CL="" diff --git a/tests/std/tests/VSO_0226079_mutex/test.cpp b/tests/std/tests/VSO_0226079_mutex/test.cpp index dd41244401..8224f83f3a 100644 --- a/tests/std/tests/VSO_0226079_mutex/test.cpp +++ b/tests/std/tests/VSO_0226079_mutex/test.cpp @@ -468,18 +468,6 @@ void test_vso_1253916() { do_shared_locked_things(shared_lock{mtx}); } -#ifndef _DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR -struct test_constexpr_ctor { - constexpr test_constexpr_ctor() {} - mutex mtx; -}; - -test_constexpr_ctor obj; -#if _HAS_CXX20 && !defined(_M_CEE) -constinit test_constexpr_ctor obj2; -#endif // _HAS_CXX20 && !defined(_M_CEE) -#endif // !_DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR - int main() { { mutex_test_fixture fixture;