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