From 7ee2dc75bdec6a8b2c6756ee4b69a4a478a955a9 Mon Sep 17 00:00:00 2001 From: MiroKaku <50670906+MiroKaku@users.noreply.github.com> Date: Mon, 8 May 2023 18:39:11 +0800 Subject: [PATCH] feat: support locks std::mutex std::timed_mutex std::recursive_mutex std::recursive_timed_mutex std::condition_variable std::condition_variable_any std::notify_all_at_thread_exit --- msvc/ucxxrt.vcxproj | 8 ++ msvc/ucxxrt.vcxproj.filters | 24 ++++ src/crt/stl/cond.cpp | 109 ++++++++++++++++ src/crt/stl/mutex.cpp | 224 ++++++++++++++++++++++++++++++++ src/crt/stl/primitives.hpp | 149 +++++++++++++++++++++ src/crt/stl/xlock.cpp | 133 +++++++++++++++++++ src/crt/stl/xmtx.cpp | 32 +++++ src/crt/stl/xmtx.hpp | 40 ++++++ src/crt/stl/xnotify.cpp | 123 ++++++++++++++++++ src/crt/stl/xtime.cpp | 116 +++++++++++++++++ src/ucrt/inc/corecrt_internal.h | 2 + src/ucrt/internal/locks.cpp | 10 ++ src/ucrt/misc/dbgrpt.cpp | 4 +- src/ucrt/misc/errno.cpp | 5 +- src/ucrt/startup/abort.cpp | 2 +- test/unittest.cpp | 21 ++- veil | 2 +- 17 files changed, 997 insertions(+), 7 deletions(-) create mode 100644 src/crt/stl/cond.cpp create mode 100644 src/crt/stl/mutex.cpp create mode 100644 src/crt/stl/primitives.hpp create mode 100644 src/crt/stl/xlock.cpp create mode 100644 src/crt/stl/xmtx.cpp create mode 100644 src/crt/stl/xmtx.hpp create mode 100644 src/crt/stl/xnotify.cpp create mode 100644 src/crt/stl/xtime.cpp diff --git a/msvc/ucxxrt.vcxproj b/msvc/ucxxrt.vcxproj index 02fb49a..452c21c 100644 --- a/msvc/ucxxrt.vcxproj +++ b/msvc/ucxxrt.vcxproj @@ -35,7 +35,9 @@ + + @@ -195,9 +197,11 @@ true true + + @@ -235,10 +239,13 @@ + + + @@ -255,6 +262,7 @@ + diff --git a/msvc/ucxxrt.vcxproj.filters b/msvc/ucxxrt.vcxproj.filters index c5a1c92..76cb679 100644 --- a/msvc/ucxxrt.vcxproj.filters +++ b/msvc/ucxxrt.vcxproj.filters @@ -118,6 +118,12 @@ ucxxrt\crt\vcruntime + + ucxxrt\crt\stl + + + ucxxrt\crt\stl + @@ -680,6 +686,24 @@ ucxxrt\ucrt\heap + + ucxxrt\crt\stl + + + ucxxrt\crt\stl + + + ucxxrt\crt\stl + + + ucxxrt\crt\stl + + + ucxxrt\crt\stl + + + ucxxrt\crt\stl + diff --git a/src/crt/stl/cond.cpp b/src/crt/stl/cond.cpp new file mode 100644 index 0000000..e64f16e --- /dev/null +++ b/src/crt/stl/cond.cpp @@ -0,0 +1,109 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +// condition variable functions + +#include +#include +#include +#include +#include + +#include "primitives.hpp" + +struct _Cnd_internal_imp_t { // condition variable implementation for ConcRT + typename std::_Aligned_storage::type cv; + + [[nodiscard]] Concurrency::details::stl_condition_variable_interface* _get_cv() noexcept { + // get pointer to implementation + return reinterpret_cast(&cv); + } +}; + +static_assert(sizeof(_Cnd_internal_imp_t) <= _Cnd_internal_imp_size, "incorrect _Cnd_internal_imp_size"); +static_assert(alignof(_Cnd_internal_imp_t) <= _Cnd_internal_imp_alignment, "incorrect _Cnd_internal_imp_alignment"); + +void _Cnd_init_in_situ(const _Cnd_t cond) { // initialize condition variable in situ + Concurrency::details::create_stl_condition_variable(cond->_get_cv()); +} + +void _Cnd_destroy_in_situ(const _Cnd_t cond) { // destroy condition variable in situ + cond->_get_cv()->destroy(); +} + +int _Cnd_init(_Cnd_t* const pcond) { // initialize + *pcond = nullptr; + + const auto cond = static_cast<_Cnd_t>(_calloc_crt(1, sizeof(_Cnd_internal_imp_t))); + if (cond == nullptr) { + return _Thrd_nomem; // report alloc failed + } + + _Cnd_init_in_situ(cond); + *pcond = cond; + return _Thrd_success; +} + +void _Cnd_destroy(const _Cnd_t cond) { // clean up + if (cond) { // something to do, do it + _Cnd_destroy_in_situ(cond); + _free_crt(cond); + } +} + +int _Cnd_wait(const _Cnd_t cond, const _Mtx_t mtx) { // wait until signaled + const auto cs = static_cast(_Mtx_getconcrtcs(mtx)); + _Mtx_clear_owner(mtx); + cond->_get_cv()->wait(cs); + _Mtx_reset_owner(mtx); + return _Thrd_success; // TRANSITION, ABI: Always returns _Thrd_success +} + +int _Cnd_timedwait(const _Cnd_t cond, const _Mtx_t mtx, const xtime* const target) { // wait until signaled or timeout + int res = _Thrd_success; + 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); + _Mtx_reset_owner(mtx); + } else { // target time specified, wait for it + xtime now; + xtime_get(&now, TIME_UTC); + _Mtx_clear_owner(mtx); + if (!cond->_get_cv()->wait_for(cs, _Xtime_diff_to_millis2(target, &now))) { // report timeout + xtime_get(&now, TIME_UTC); + if (_Xtime_diff_to_millis2(target, &now) == 0) { + res = _Thrd_timedout; + } + } + _Mtx_reset_owner(mtx); + } + return res; +} + +int _Cnd_signal(const _Cnd_t cond) { // release one waiting thread + cond->_get_cv()->notify_one(); + return _Thrd_success; // TRANSITION, ABI: Always returns _Thrd_success +} + +int _Cnd_broadcast(const _Cnd_t cond) { // release all waiting threads + cond->_get_cv()->notify_all(); + return _Thrd_success; // TRANSITION, ABI: Always returns _Thrd_success +} + +/* + * This file is derived from software bearing the following + * restrictions: + * + * (c) Copyright William E. Kempf 2001 + * + * Permission to use, copy, modify, distribute and sell this + * software and its documentation for any purpose is hereby + * granted without fee, provided that the above copyright + * notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting + * documentation. William E. Kempf makes no representations + * about the suitability of this software for any purpose. + * It is provided "as is" without express or implied warranty. + */ diff --git a/src/crt/stl/mutex.cpp b/src/crt/stl/mutex.cpp new file mode 100644 index 0000000..05aa972 --- /dev/null +++ b/src/crt/stl/mutex.cpp @@ -0,0 +1,224 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +// mutex functions + +#include +#include +#include +#include +#include +#include + +#include "primitives.hpp" + +extern "C" _CRTIMP2_PURE void __cdecl _Thrd_abort(const char* msg) { // abort on precondition failure + DbgPrintEx(DPFLTR_DEFAULT_ID, DPFLTR_ERROR_LEVEL, "%s\n", msg); + abort(); +} + +#if defined(_THREAD_CHECK) || defined(_DEBUG) +#define _THREAD_CHECKX 1 +#else // defined(_THREAD_CHECK) || defined(_DEBUG) +#define _THREAD_CHECKX 0 +#endif // defined(_THREAD_CHECK) || defined(_DEBUG) + +#if _THREAD_CHECKX +#define _THREAD_QUOTX(x) #x +#define _THREAD_QUOT(x) _THREAD_QUOTX(x) +#define _THREAD_ASSERT(expr, msg) ((expr) ? (void) 0 : _Thrd_abort(__FILE__ "(" _THREAD_QUOT(__LINE__) "): " msg)) +#else // _THREAD_CHECKX +#define _THREAD_ASSERT(expr, msg) ((void) 0) +#endif // _THREAD_CHECKX + +// TRANSITION, ABI: preserved for binary compatibility +enum class __stl_sync_api_modes_enum { normal, win7, vista, concrt }; +extern "C" _CRTIMP2 void __cdecl __set_stl_sync_api_mode(__stl_sync_api_modes_enum) {} + +struct _Mtx_internal_imp_t { // ConcRT mutex + int type; + typename std::_Aligned_storage::type cs; + long thread_id; + int count; + Concurrency::details::stl_critical_section_interface* _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"); + +void __cdecl _Mtx_init_in_situ(_Mtx_t mtx, int type) { // initialize mutex in situ + Concurrency::details::create_stl_critical_section(mtx->_get_cs()); + mtx->thread_id = -1; + mtx->type = type; + mtx->count = 0; +} + +void __cdecl _Mtx_destroy_in_situ(_Mtx_t mtx) { // destroy mutex in situ + _THREAD_ASSERT(mtx->count == 0, "mutex destroyed while busy"); + mtx->_get_cs()->destroy(); +} + +int __cdecl _Mtx_init(_Mtx_t* mtx, int type) { // initialize mutex + *mtx = nullptr; + + _Mtx_t mutex = static_cast<_Mtx_t>(_calloc_crt(1, sizeof(_Mtx_internal_imp_t))); + + if (mutex == nullptr) { + return _Thrd_nomem; // report alloc failed + } + + _Mtx_init_in_situ(mutex, type); + + *mtx = mutex; + return _Thrd_success; +} + +void __cdecl _Mtx_destroy(_Mtx_t mtx) { // destroy mutex + if (mtx) { // something to do, do it + _Mtx_destroy_in_situ(mtx); + _free_crt(mtx); + } +} + +static int mtx_do_lock(_Mtx_t mtx, const xtime* target) { // lock mutex + if ((mtx->type & ~_Mtx_recursive) == _Mtx_plain) { // set the lock + if (mtx->thread_id != static_cast(reinterpret_cast(PsGetCurrentThreadId()))) { // not current thread, do lock + mtx->_get_cs()->lock(); + mtx->thread_id = static_cast(reinterpret_cast(PsGetCurrentThreadId())); + } + ++mtx->count; + + return _Thrd_success; + } else { // handle timed or recursive mutex + int res = STATUS_TIMEOUT; + if (target == nullptr) { // no target --> plain wait (i.e. infinite timeout) + if (mtx->thread_id != static_cast(reinterpret_cast(PsGetCurrentThreadId()))) { + mtx->_get_cs()->lock(); + } + + res = STATUS_WAIT_0; + + } else if (target->sec < 0 || target->sec == 0 && target->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 != static_cast(reinterpret_cast(PsGetCurrentThreadId()))) { // not this thread, lock it + if (mtx->_get_cs()->try_lock()) { + res = STATUS_WAIT_0; + } else { + res = STATUS_TIMEOUT; + } + } else { + res = STATUS_WAIT_0; + } + + } else { // check timeout + xtime now; + xtime_get(&now, TIME_UTC); + while (now.sec < target->sec || now.sec == target->sec && now.nsec < target->nsec) { // time has not expired + if (mtx->thread_id == static_cast(reinterpret_cast(PsGetCurrentThreadId())) + || mtx->_get_cs()->try_lock_for(_Xtime_diff_to_millis2(target, &now))) { // stop waiting + res = STATUS_WAIT_0; + break; + } else { + res = STATUS_TIMEOUT; + } + + xtime_get(&now, TIME_UTC); + } + } + + if (res == STATUS_WAIT_0 || res == STATUS_ABANDONED) { + if (1 < ++mtx->count) { // check count + if ((mtx->type & _Mtx_recursive) != _Mtx_recursive) { // not recursive, fixup count + --mtx->count; + res = STATUS_TIMEOUT; + } + } else { + mtx->thread_id = static_cast(reinterpret_cast(PsGetCurrentThreadId())); + } + } + + switch (res) { + case STATUS_WAIT_0: + case STATUS_ABANDONED: + return _Thrd_success; + + case STATUS_TIMEOUT: + if (target == nullptr || (target->sec == 0 && target->nsec == 0)) { + return _Thrd_busy; + } else { + return _Thrd_timedout; + } + + default: + return _Thrd_error; + } + } +} + +int __cdecl _Mtx_unlock(_Mtx_t mtx) { // unlock mutex + _THREAD_ASSERT( + 1 <= mtx->count && mtx->thread_id == static_cast(reinterpret_cast(PsGetCurrentThreadId())), "unlock of unowned mutex"); + + if (--mtx->count == 0) { // leave critical section + mtx->thread_id = -1; + mtx->_get_cs()->unlock(); + } + return _Thrd_success; // TRANSITION, ABI: always returns _Thrd_success +} + +int __cdecl _Mtx_lock(_Mtx_t mtx) { // lock mutex + return mtx_do_lock(mtx, nullptr); +} + +int __cdecl _Mtx_trylock(_Mtx_t mtx) { // attempt to lock try_mutex + xtime xt; + _THREAD_ASSERT((mtx->type & (_Mtx_try | _Mtx_timed)) != 0, "trylock not supported by mutex"); + xt.sec = 0; + xt.nsec = 0; + return mtx_do_lock(mtx, &xt); +} + +int __cdecl _Mtx_timedlock(_Mtx_t mtx, const xtime* xt) { // attempt to lock timed mutex + int res; + + _THREAD_ASSERT((mtx->type & _Mtx_timed) != 0, "timedlock not supported by mutex"); + res = mtx_do_lock(mtx, xt); + return res == _Thrd_busy ? _Thrd_timedout : res; +} + +int __cdecl _Mtx_current_owns(_Mtx_t mtx) { // test if current thread owns mutex + return mtx->count != 0 && mtx->thread_id == static_cast(reinterpret_cast(PsGetCurrentThreadId())); +} + +void* __cdecl _Mtx_getconcrtcs(_Mtx_t mtx) { // get internal cs impl + return mtx->_get_cs(); +} + +void __cdecl _Mtx_clear_owner(_Mtx_t mtx) { // set owner to nobody + mtx->thread_id = -1; + --mtx->count; +} + +void __cdecl _Mtx_reset_owner(_Mtx_t mtx) { // set owner to current thread + mtx->thread_id = static_cast(reinterpret_cast(PsGetCurrentThreadId())); + ++mtx->count; +} + +/* + * This file is derived from software bearing the following + * restrictions: + * + * (c) Copyright William E. Kempf 2001 + * + * Permission to use, copy, modify, distribute and sell this + * software and its documentation for any purpose is hereby + * granted without fee, provided that the above copyright + * notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting + * documentation. William E. Kempf makes no representations + * about the suitability of this software for any purpose. + * It is provided "as is" without express or implied warranty. + */ diff --git a/src/crt/stl/primitives.hpp b/src/crt/stl/primitives.hpp new file mode 100644 index 0000000..17a89c9 --- /dev/null +++ b/src/crt/stl/primitives.hpp @@ -0,0 +1,149 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#pragma once + +#include +#include + +//#include "awint.hpp" + +namespace Concurrency { + namespace details { + class __declspec(novtable) stl_critical_section_interface { + public: + virtual void lock() = 0; + virtual bool try_lock() = 0; + virtual bool try_lock_for(unsigned int) = 0; + virtual void unlock() = 0; + virtual void destroy() = 0; + }; + + class __declspec(novtable) stl_condition_variable_interface { + public: + virtual void wait(stl_critical_section_interface*) = 0; + virtual bool wait_for(stl_critical_section_interface*, unsigned int) = 0; + virtual void notify_one() = 0; + virtual void notify_all() = 0; + virtual void destroy() = 0; + }; + + class stl_critical_section_km final : public stl_critical_section_interface { + public: + stl_critical_section_km() { + ExInitializeFastMutex(&m_lock); + } + + ~stl_critical_section_km() = delete; + stl_critical_section_km(const stl_critical_section_km&) = delete; + stl_critical_section_km& operator=(const stl_critical_section_km&) = delete; + + void destroy() override {} + + void lock() override { + ExAcquireFastMutex(&m_lock); + } + + bool try_lock() override { + return ExTryToAcquireFastMutex(&m_lock) != 0; + } + + bool try_lock_for(unsigned int) override { + // STL will call try_lock_for once again if this call will not succeed + return stl_critical_section_km::try_lock(); + } + + void unlock() override { + ExReleaseFastMutex(&m_lock); + } + + PFAST_MUTEX native_handle() { + return &m_lock; + } + + private: + FAST_MUTEX m_lock{}; + }; + + class stl_condition_variable_km final : public stl_condition_variable_interface { + public: + stl_condition_variable_km() { + KeInitializeSemaphore(&m_semaphore, 0, LONG_MAX); + } + + ~stl_condition_variable_km() = delete; + stl_condition_variable_km(const stl_condition_variable_km&) = delete; + stl_condition_variable_km& operator=(const stl_condition_variable_km&) = delete; + + void destroy() override {} + + void wait(stl_critical_section_interface* lock) override { + if (!stl_condition_variable_km::wait_for(lock, INFINITE)) { + std::terminate(); + } + } + + bool wait_for(stl_critical_section_interface* lock, unsigned int timeout) override { + + LARGE_INTEGER wait_time; + wait_time.QuadPart = Int32x32To64(timeout, -10000); + + lock->unlock(); + { + if (InterlockedIncrement(&m_wait_count) > 0) { + + const auto status = KeWaitForSingleObject(&m_semaphore, Executive, + KernelMode, FALSE, timeout == INFINITE ? nullptr : &wait_time); + if (status != STATUS_SUCCESS) { + _set_errno(status); + return false; + } + } + } + lock->lock(); + return true; + } + + void notify_one() override { + if (InterlockedCompareExchange(&m_wait_count, 0, 0) > 0) { + const long count = InterlockedDecrement(&m_wait_count); + if (count >= 0) { + (void)KeReleaseSemaphore(&m_semaphore, SEMAPHORE_INCREMENT, 1, false); + } + } + } + + void notify_all() override { + if (InterlockedCompareExchange(&m_wait_count, 0, 0) > 0) { + long count; + + do { + count = InterlockedDecrement(&m_wait_count); + if (count >= 0) { + (void)KeReleaseSemaphore(&m_semaphore, SEMAPHORE_INCREMENT, 1, false); + } + } while (count); + } + } + + private: + KSEMAPHORE m_semaphore{}; + volatile long m_wait_count = 0l; + }; + + inline void create_stl_critical_section(stl_critical_section_interface* p) { + new (p) stl_critical_section_km; + } + + inline void create_stl_condition_variable(stl_condition_variable_interface* p) { + new (p) stl_condition_variable_km; + } + + const size_t stl_critical_section_max_size = sizeof(stl_critical_section_km); + const size_t stl_condition_variable_max_size = sizeof(stl_condition_variable_km); + + // concrt, vista, and win7 alignments are all identical to alignof(void*) + const size_t stl_critical_section_max_alignment = alignof(stl_critical_section_km); + const size_t stl_condition_variable_max_alignment = alignof(stl_condition_variable_km); + } // namespace details +} // namespace Concurrency diff --git a/src/crt/stl/xlock.cpp b/src/crt/stl/xlock.cpp new file mode 100644 index 0000000..e196c7d --- /dev/null +++ b/src/crt/stl/xlock.cpp @@ -0,0 +1,133 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +// global lock for locales, etc. + +#include + +#include +#include + +#include "xmtx.hpp" + +_STD_BEGIN + +constexpr int _Max_lock = 8; // must be power of two + +#pragma warning(disable : 4074) +#pragma init_seg(compiler) + +static _Rmtx mtx[_Max_lock]; +static long init = -1; + +#if !defined(MRTDLL) + +__thiscall _Init_locks::_Init_locks() noexcept { // initialize locks + if (InterlockedIncrement(&init) == 0) { + for (auto& elem : mtx) { + _Mtxinit(&elem); + } + } +} + +__thiscall _Init_locks::~_Init_locks() noexcept { // clean up locks + if (InterlockedDecrement(&init) < 0) { + for (auto& elem : mtx) { + _Mtxdst(&elem); + } + } +} + +#endif + +void __cdecl _Init_locks::_Init_locks_ctor(_Init_locks*) noexcept { // initialize locks + if (InterlockedIncrement(&init) == 0) { + for (auto& elem : mtx) { + _Mtxinit(&elem); + } + } +} + +void __cdecl _Init_locks::_Init_locks_dtor(_Init_locks*) noexcept { // clean up locks + if (InterlockedDecrement(&init) < 0) { + for (auto& elem : mtx) { + _Mtxdst(&elem); + } + } +} + +static _Init_locks initlocks; + +#if !defined(MRTDLL) + +__thiscall _Lockit::_Lockit() noexcept : _Locktype(0) { // lock default mutex + if (_Locktype == _LOCK_LOCALE) { + _lock_locales(); + } else { + _Mtxlock(&mtx[0]); + } +} + +__thiscall _Lockit::_Lockit(int kind) noexcept : _Locktype(kind) { // lock the mutex + if (_Locktype == _LOCK_LOCALE) { + _lock_locales(); + } else if (_Locktype < _Max_lock) { + _Mtxlock(&mtx[_Locktype]); + } +} + +__thiscall _Lockit::~_Lockit() noexcept { // unlock the mutex + if (_Locktype == _LOCK_LOCALE) { + _unlock_locales(); + } else if (_Locktype < _Max_lock) { + _Mtxunlock(&mtx[_Locktype]); + } +} + +#endif + +void __cdecl _Lockit::_Lockit_ctor(_Lockit*) noexcept { // lock default mutex + _Mtxlock(&mtx[0]); +} + +void __cdecl _Lockit::_Lockit_ctor(_Lockit* _This, int kind) noexcept { // lock the mutex + if (kind == _LOCK_LOCALE) { + _lock_locales(); + } else { + _This->_Locktype = kind & (_Max_lock - 1); + _Mtxlock(&mtx[_This->_Locktype]); + } +} + +void __cdecl _Lockit::_Lockit_dtor(_Lockit* _This) noexcept { // unlock the mutex + _Mtxunlock(&mtx[_This->_Locktype]); +} + +_RELIABILITY_CONTRACT +void __cdecl _Lockit::_Lockit_ctor(int kind) noexcept { // lock the mutex + if (kind == _LOCK_LOCALE) { + _lock_locales(); + } else { + _Mtxlock(&mtx[kind & (_Max_lock - 1)]); + } +} + +_RELIABILITY_CONTRACT +void __cdecl _Lockit::_Lockit_dtor(int kind) noexcept { // unlock the mutex + if (kind == _LOCK_LOCALE) { + _unlock_locales(); + } else { + _Mtxunlock(&mtx[kind & (_Max_lock - 1)]); + } +} + +_EXTERN_C +void _Lock_at_thread_exit_mutex() { // lock the at-thread-exit mutex + _Mtxlock(&mtx[_LOCK_AT_THREAD_EXIT]); +} +void _Unlock_at_thread_exit_mutex() { // unlock the at-thread-exit mutex + _Mtxunlock(&mtx[_LOCK_AT_THREAD_EXIT]); +} +_END_EXTERN_C + +_STD_END diff --git a/src/crt/stl/xmtx.cpp b/src/crt/stl/xmtx.cpp new file mode 100644 index 0000000..52a6719 --- /dev/null +++ b/src/crt/stl/xmtx.cpp @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +// mutex support + +#include "xmtx.hpp" + +//#include "awint.hpp" + +_EXTERN_C_UNLESS_PURE + +// Win32 critical sections are recursive + +void __CLRCALL_PURE_OR_CDECL _Mtxinit(_Rmtx* _Mtx) noexcept { // initialize mutex + (void)ExInitializeResourceLite(_Mtx); +} + +void __CLRCALL_PURE_OR_CDECL _Mtxdst(_Rmtx* _Mtx) noexcept { // delete mutex + (void)ExDeleteResourceLite(_Mtx); +} + +_RELIABILITY_CONTRACT +void __CLRCALL_PURE_OR_CDECL _Mtxlock(_Rmtx* _Mtx) noexcept { // lock mutex + ExEnterCriticalRegionAndAcquireResourceExclusive(_Mtx); +} + +_RELIABILITY_CONTRACT +void __CLRCALL_PURE_OR_CDECL _Mtxunlock(_Rmtx* _Mtx) noexcept { // unlock mutex + ExReleaseResourceAndLeaveCriticalRegion(_Mtx); +} + +_END_EXTERN_C_UNLESS_PURE diff --git a/src/crt/stl/xmtx.hpp b/src/crt/stl/xmtx.hpp new file mode 100644 index 0000000..602e203 --- /dev/null +++ b/src/crt/stl/xmtx.hpp @@ -0,0 +1,40 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#pragma once +#ifndef _XMTX +#define _XMTX +#include + +#include + +//#include + +#pragma pack(push, _CRT_PACKING) +#pragma warning(push, 3) +#pragma push_macro("new") +#undef new + +_EXTERN_C_UNLESS_PURE + +using _Rmtx = ERESOURCE; + +#ifdef _M_CEE_PURE +void __clrcall _Mtxinit(_Rmtx*) noexcept; +void __clrcall _Mtxdst(_Rmtx*) noexcept; +void __clrcall _Mtxlock(_Rmtx*) noexcept; +void __clrcall _Mtxunlock(_Rmtx*) noexcept; + +#else // _M_CEE_PURE +_MRTIMP2 void __cdecl _Mtxinit(_Rmtx*) noexcept; +_MRTIMP2 void __cdecl _Mtxdst(_Rmtx*) noexcept; +_MRTIMP2 void __cdecl _Mtxlock(_Rmtx*) noexcept; +_MRTIMP2 void __cdecl _Mtxunlock(_Rmtx*) noexcept; +#endif // _M_CEE_PURE + +_END_EXTERN_C_UNLESS_PURE + +#pragma pop_macro("new") +#pragma warning(pop) +#pragma pack(pop) +#endif // _XMTX diff --git a/src/crt/stl/xnotify.cpp b/src/crt/stl/xnotify.cpp new file mode 100644 index 0000000..3378291 --- /dev/null +++ b/src/crt/stl/xnotify.cpp @@ -0,0 +1,123 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +// thread exit notification functions + +#include +#include + +//#include + +constexpr int _Nitems = 20; + +namespace { + struct _At_thread_exit_data { // data for condition-variable slot + _Thrd_t id; + _Mtx_t mtx; + _Cnd_t cnd; + int* res; + }; + + struct _At_thread_exit_block { // block of condition-variable slots + _At_thread_exit_data data[_Nitems]; + int num_used; + _At_thread_exit_block* next; + }; + + _At_thread_exit_block _Thread_exit_data; +} // unnamed namespace + +_EXTERN_C + +void _Lock_at_thread_exit_mutex(); +void _Unlock_at_thread_exit_mutex(); + +void _Cnd_register_at_thread_exit( + _Cnd_t cnd, _Mtx_t mtx, int* p) { // register condition variable and mutex for cleanup at thread exit + // find block with available space + _At_thread_exit_block* block = &_Thread_exit_data; + + _Lock_at_thread_exit_mutex(); + while (block != nullptr) { // loop through list of blocks + if (block->num_used == _Nitems) { // block is full; move to next block and allocate + if (block->next == nullptr) { + block->next = static_cast<_At_thread_exit_block*>(calloc(1, sizeof(_At_thread_exit_block))); + } + + block = block->next; + } else { // found block with available space + for (int i = 0; i < _Nitems; ++i) { // find empty slot + if (block->data[i].mtx == nullptr) { // store into empty slot + block->data[i].id._Id = static_cast<_Thrd_id_t>(reinterpret_cast(PsGetCurrentThreadId())); + block->data[i].mtx = mtx; + block->data[i].cnd = cnd; + block->data[i].res = p; + ++block->num_used; + break; + } + } + block = nullptr; + } + } + _Unlock_at_thread_exit_mutex(); +} + +void _Cnd_unregister_at_thread_exit(_Mtx_t mtx) { // unregister condition variable/mutex for cleanup at thread exit + // find condition variables waiting for this thread to exit + _At_thread_exit_block* block = &_Thread_exit_data; + + _Lock_at_thread_exit_mutex(); + while (block != nullptr) { // loop through list of blocks + for (int i = 0; block->num_used != 0 && i < _Nitems; ++i) { + if (block->data[i].mtx == mtx) { // release slot + block->data[i].mtx = nullptr; + --block->num_used; + } + } + + block = block->next; + } + _Unlock_at_thread_exit_mutex(); +} + +void _Cnd_do_broadcast_at_thread_exit() { // notify condition variables waiting for this thread to exit + // find condition variables waiting for this thread to exit + _At_thread_exit_block* block = &_Thread_exit_data; + const unsigned int currentThreadId = _Thrd_id(); + + _Lock_at_thread_exit_mutex(); + while (block != nullptr) { // loop through list of blocks + for (int i = 0; block->num_used != 0 && i < _Nitems; ++i) { + if (block->data[i].mtx != nullptr && block->data[i].id._Id == currentThreadId) { // notify and release slot + if (block->data[i].res) { + *block->data[i].res = 1; + } + _Mtx_unlock(block->data[i].mtx); + _Cnd_broadcast(block->data[i].cnd); + block->data[i].mtx = nullptr; + --block->num_used; + } + } + + block = block->next; + } + _Unlock_at_thread_exit_mutex(); +} + +_END_EXTERN_C + +/* + * This file is derived from software bearing the following + * restrictions: + * + * (c) Copyright William E. Kempf 2001 + * + * Permission to use, copy, modify, distribute and sell this + * software and its documentation for any purpose is hereby + * granted without fee, provided that the above copyright + * notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting + * documentation. William E. Kempf makes no representations + * about the suitability of this software for any purpose. + * It is provided "as is" without express or implied warranty. + */ diff --git a/src/crt/stl/xtime.cpp b/src/crt/stl/xtime.cpp new file mode 100644 index 0000000..4c7aa0c --- /dev/null +++ b/src/crt/stl/xtime.cpp @@ -0,0 +1,116 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +// xtime functions + +#include +#include + +//#include "awint.hpp" + +constexpr long _Nsec_per_sec = 1000000000L; +constexpr long _Nsec_per_msec = 1000000L; +constexpr int _Msec_per_sec = 1000; + +static void xtime_normalize(xtime* xt) { // adjust so that 0 <= nsec < 1 000 000 000 + while (xt->nsec < 0) { // normalize target time + xt->sec -= 1; + xt->nsec += _Nsec_per_sec; + } + while (_Nsec_per_sec <= xt->nsec) { // normalize target time + xt->sec += 1; + xt->nsec -= _Nsec_per_sec; + } +} + +static xtime xtime_diff(const xtime* xt, + const xtime* now) { // return xtime object holding difference between xt and now, treating negative difference as 0 + xtime diff = *xt; + xtime_normalize(&diff); + if (diff.nsec < now->nsec) { // avoid underflow + diff.sec -= now->sec + 1; + diff.nsec += _Nsec_per_sec - now->nsec; + } else { // no underflow + diff.sec -= now->sec; + diff.nsec -= now->nsec; + } + + if (diff.sec < 0 || (diff.sec == 0 && diff.nsec <= 0)) { // time is zero + diff.sec = 0; + diff.nsec = 0; + } + return diff; +} + + +constexpr long long _Epoch = 0x19DB1DED53E8000LL; +constexpr long _Nsec100_per_sec = _Nsec_per_sec / 100; + +_EXTERN_C + +_CRTIMP2_PURE long long __cdecl _Xtime_get_ticks() { // get system time in 100-nanosecond intervals since the epoch + LARGE_INTEGER li; + KeQuerySystemTimePrecise(&li); + return ((static_cast(li.HighPart)) << 32) + static_cast(li.LowPart) - _Epoch; +} + +static void sys_get_time(xtime* xt) { // get system time with nanosecond resolution + unsigned long long now = _Xtime_get_ticks(); + xt->sec = static_cast<__time64_t>(now / _Nsec100_per_sec); + xt->nsec = static_cast(now % _Nsec100_per_sec) * 100; +} + +_CRTIMP2_PURE long __cdecl _Xtime_diff_to_millis2(const xtime* xt1, const xtime* xt2) { // convert time to milliseconds + xtime diff = xtime_diff(xt1, xt2); + return static_cast(diff.sec * _Msec_per_sec + (diff.nsec + _Nsec_per_msec - 1) / _Nsec_per_msec); +} + +_CRTIMP2_PURE long __cdecl _Xtime_diff_to_millis(const xtime* xt) { // convert time to milliseconds + xtime now; + xtime_get(&now, TIME_UTC); + return _Xtime_diff_to_millis2(xt, &now); +} + +_CRTIMP2_PURE int __cdecl xtime_get(xtime* xt, int type) { // get current time + if (type != TIME_UTC || xt == nullptr) { + type = 0; + } else { + sys_get_time(xt); + } + + return type; +} + +_CRTIMP2_PURE long long __cdecl _Query_perf_counter() { // get current value of performance counter + return KeQueryPerformanceCounter(nullptr).QuadPart; +} + +_CRTIMP2_PURE long long __cdecl _Query_perf_frequency() { // get frequency of performance counter + static std::atomic freq_cached{0}; + long long freq = freq_cached.load(std::memory_order_relaxed); + if (freq == 0) { + LARGE_INTEGER li; + KeQueryPerformanceCounter(&li); // always succeeds + freq = li.QuadPart; // doesn't change after system boot + freq_cached.store(freq, std::memory_order_relaxed); + } + return freq; +} + +_END_EXTERN_C + +/* + * This file is derived from software bearing the following + * restrictions: + * + * (c) Copyright William E. Kempf 2001 + * + * Permission to use, copy, modify, distribute and sell this + * software and its documentation for any purpose is hereby + * granted without fee, provided that the above copyright + * notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting + * documentation. William E. Kempf makes no representations + * about the suitability of this software for any purpose. + * It is provided "as is" without express or implied warranty. + */ diff --git a/src/ucrt/inc/corecrt_internal.h b/src/ucrt/inc/corecrt_internal.h index 2ceb6be..42d399c 100644 --- a/src/ucrt/inc/corecrt_internal.h +++ b/src/ucrt/inc/corecrt_internal.h @@ -152,6 +152,8 @@ int __cdecl __acrt_errno_from_os_error(long); typedef enum __acrt_lock_id { __acrt_exit_lock, + __acrt_locale_lock, + __acrt_time_lock, __acrt_lock_count } __acrt_lock_id; diff --git a/src/ucrt/internal/locks.cpp b/src/ucrt/internal/locks.cpp index dafca21..3a30c6d 100644 --- a/src/ucrt/internal/locks.cpp +++ b/src/ucrt/internal/locks.cpp @@ -63,3 +63,13 @@ extern "C" __acrt_lock_id __cdecl __acrt_select_exit_lock() { return __acrt_exit_lock; } + +extern "C" void __cdecl _lock_locales() +{ + __acrt_lock(__acrt_locale_lock); +} + +extern "C" void __cdecl _unlock_locales() +{ + __acrt_unlock(__acrt_locale_lock); +} diff --git a/src/ucrt/misc/dbgrpt.cpp b/src/ucrt/misc/dbgrpt.cpp index 026dd0f..6426ddb 100644 --- a/src/ucrt/misc/dbgrpt.cpp +++ b/src/ucrt/misc/dbgrpt.cpp @@ -107,7 +107,7 @@ int __cdecl _CrtDbgReport( _vsnprintf(buffer, count, format, args); - DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, + DbgPrintEx(DPFLTR_DEFAULT_ID, DPFLTR_ERROR_LEVEL, "================= Microsft Visual C++ Debug Library ================\n" "\n" "%s" @@ -157,7 +157,7 @@ int __cdecl _CrtDbgReportW( _vsnwprintf(buffer, count, format, args); - DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, + DbgPrintEx(DPFLTR_DEFAULT_ID, DPFLTR_ERROR_LEVEL, "================= Microsft Visual C++ Debug Library ================\n" "\n" "%ls" diff --git a/src/ucrt/misc/errno.cpp b/src/ucrt/misc/errno.cpp index 6998471..0ea5897 100644 --- a/src/ucrt/misc/errno.cpp +++ b/src/ucrt/misc/errno.cpp @@ -34,7 +34,7 @@ static errentry const errtable[] { STATUS_BUFFER_OVERFLOW, ENOMEM }, // 7 { STATUS_NO_MEMORY, ENOMEM }, // 8 { STATUS_INSUFFICIENT_RESOURCES,ENOMEM }, // 9 - { STATUS_IMAGE_SUBSYSTEM_NOT_PRESENT, E2BIG }, // 10 + { STATUS_IMAGE_SUBSYSTEM_NOT_PRESENT, E2BIG }, // 10 { STATUS_INVALID_IMAGE_FORMAT, ENOEXEC }, // 11 { STATUS_ACCESS_VIOLATION, EINVAL }, // 12 { STATUS_DATA_ERROR, EINVAL }, // 13 @@ -53,6 +53,9 @@ static errentry const errtable[] { STATUS_CHILD_PROCESS_BLOCKED, EAGAIN }, // 89 { STATUS_PIPE_BROKEN, EPIPE }, // 109 { STATUS_DISK_FULL, ENOSPC }, // 112 + { STATUS_PROTOCOL_NOT_SUPPORTED, EPROTONOSUPPORT }, // 135 + { STATUS_TIMEOUT, ETIME }, // 137 + { STATUS_TIMEOUT, ETIMEDOUT }, // 138 { STATUS_DIRECTORY_NOT_EMPTY, ENOTEMPTY }, // 145 { STATUS_NOT_LOCKED, EACCES }, // 158 { STATUS_OBJECT_NAME_INVALID, ENOENT }, // 161 diff --git a/src/ucrt/startup/abort.cpp b/src/ucrt/startup/abort.cpp index 1b37488..e8154a0 100644 --- a/src/ucrt/startup/abort.cpp +++ b/src/ucrt/startup/abort.cpp @@ -47,7 +47,7 @@ extern "C" void __cdecl _UCXXRT__abort() #ifdef _DEBUG if (__abort_behavior & _WRITE_ABORT_MSG) { - DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, + DbgPrintEx(DPFLTR_DEFAULT_ID, DPFLTR_ERROR_LEVEL, "Microsoft Visual C++ Runtime Library\n" "\n" "Runtime Error!\n" diff --git a/test/unittest.cpp b/test/unittest.cpp index 19a6daa..34b62d5 100644 --- a/test/unittest.cpp +++ b/test/unittest.cpp @@ -7,12 +7,13 @@ #include #include #include +#include #ifndef ASSERT # define ASSERT assert #endif -#define LOG(Format, ...) DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "[ucxxrt] [" __FUNCTION__ ":%u]: " Format "\n", __LINE__, ## __VA_ARGS__) +#define LOG(Format, ...) DbgPrintEx(DPFLTR_DEFAULT_ID, DPFLTR_ERROR_LEVEL, "[ucxxrt] [" __FUNCTION__ ":%u]: " Format "\n", __LINE__, ## __VA_ARGS__) namespace UnitTest { @@ -57,7 +58,7 @@ namespace UnitTest int Val2 = static_cast(Val1); ASSERT(Val2 == 1); - LOG("%f == %d", Val1, Val2); + LOG("1.6f == %d", Val2); } @@ -288,6 +289,20 @@ namespace UnitTest } } + + void TEST(Mutex)() + { + std::mutex Mutex; + auto Guard = std::unique_lock(Mutex); + } + + + void TEST(ConditionVariable)() + { + std::condition_variable cv; + cv.notify_one(); + } + } namespace Main @@ -309,6 +324,8 @@ namespace Main TEST_PUSH(Realloc); TEST_PUSH(SEH); TEST_PUSH(SETranslate); + TEST_PUSH(Mutex); + TEST_PUSH(ConditionVariable); for (const auto& Test : TestVec) { Test(); diff --git a/veil b/veil index 8bad105..63b8395 160000 --- a/veil +++ b/veil @@ -1 +1 @@ -Subproject commit 8bad105a00b9112c69857cab4287470851b3a68c +Subproject commit 63b8395a347cf86d6484dc79a2f98d01eaaf68b8