diff options
author | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2022-06-15 21:40:30 +0000 |
---|---|---|
committer | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2022-06-15 21:40:30 +0000 |
commit | cd2ff9bf36f9171338c2529cdc8945fdd1e6a9fb (patch) | |
tree | 9a4daaa5e2929bd54c4ded783bdafe762e664823 | |
parent | cd00c88d44e8d798c9d435bb2e5fa3b713a7256a (diff) | |
parent | 0765ef7bf2efebc246677624917669682e8cc1f3 (diff) | |
download | libcxxabi-aml_tz3_314012010.tar.gz |
Snap for 8730993 from 0765ef7bf2efebc246677624917669682e8cc1f3 to mainline-tzdata3-releaseaml_tz3_314012070aml_tz3_314012050aml_tz3_314012010aml_tz3_313110000aml_tz3_312511020aml_tz3_312511010aml_tz3_312410020aml_tz3_312410010android12-mainline-tzdata3-releaseaml_tz3_314012010
Change-Id: I1f39aa84f389b711cabbc7f9caa70f42999ff016
-rw-r--r-- | Android.bp | 28 | ||||
-rw-r--r-- | include/__cxxabi_config.h | 4 | ||||
-rw-r--r-- | include/cxxabi.h | 2 | ||||
-rw-r--r-- | src/cxa_exception.cpp | 33 | ||||
-rw-r--r-- | src/cxa_guard.cpp | 247 | ||||
-rw-r--r-- | src/cxa_guard_impl.h | 568 | ||||
-rw-r--r-- | src/include/atomic_support.h | 32 | ||||
-rw-r--r-- | test/guard_test_basic.pass.cpp | 154 | ||||
-rw-r--r-- | test/guard_threaded_test.pass.cpp | 419 |
9 files changed, 241 insertions, 1246 deletions
@@ -41,7 +41,6 @@ license { name: "external_libcxxabi_license", visibility: [":__subpackages__"], license_kinds: [ - "SPDX-license-identifier-Apache-2.0", "SPDX-license-identifier-BSD", "SPDX-license-identifier-MIT", "SPDX-license-identifier-NCSA", @@ -176,30 +175,3 @@ cc_fuzz { "src/cxa_demangle.cpp", ], } - -// Export libc++abi headers for inclusion in the musl sysroot. -genrule { - name: "libc_musl_sysroot_libc++abi_headers", - visibility: ["//external/musl"], - srcs: [ - "NOTICE", - "include/**/*", - ], - out: ["libc_musl_sysroot_libc++abi_headers.zip"], - tools: [ - "soong_zip", - "zip2zip", - ], - cmd: "LIBCXXABI_DIR=$$(dirname $(location NOTICE)) && " + - "$(location soong_zip) -o $(genDir)/sysroot.zip -symlinks=false" + - // NOTICE - " -j -f $(location NOTICE) " + - // headers - " -P include/c++ " + - " -C $${LIBCXXABI_DIR}/include " + - " -D $${LIBCXXABI_DIR}/include " + - " && " + - "$(location zip2zip) -i $(genDir)/sysroot.zip -o $(out) " + - " include/**/*:include " + - " NOTICE:NOTICE.libc++abi", -} diff --git a/include/__cxxabi_config.h b/include/__cxxabi_config.h index 1f60167..46f5914 100644 --- a/include/__cxxabi_config.h +++ b/include/__cxxabi_config.h @@ -70,8 +70,4 @@ #define _LIBCXXABI_NO_CFI #endif -#if defined(__arm__) -# define _LIBCXXABI_GUARD_ABI_ARM -#endif - #endif // ____CXXABI_CONFIG_H diff --git a/include/cxxabi.h b/include/cxxabi.h index 2926081..c6724ad 100644 --- a/include/cxxabi.h +++ b/include/cxxabi.h @@ -78,7 +78,7 @@ extern _LIBCXXABI_FUNC_VIS _LIBCXXABI_NORETURN void __cxa_pure_virtual(void); extern _LIBCXXABI_FUNC_VIS _LIBCXXABI_NORETURN void __cxa_deleted_virtual(void); // 3.3.2 One-time Construction API -#if defined(_LIBCXXABI_GUARD_ABI_ARM) +#ifdef __arm__ extern _LIBCXXABI_FUNC_VIS int __cxa_guard_acquire(uint32_t *); extern _LIBCXXABI_FUNC_VIS void __cxa_guard_release(uint32_t *); extern _LIBCXXABI_FUNC_VIS void __cxa_guard_abort(uint32_t *); diff --git a/src/cxa_exception.cpp b/src/cxa_exception.cpp index 9e650b5..8d30e5c 100644 --- a/src/cxa_exception.cpp +++ b/src/cxa_exception.cpp @@ -343,11 +343,8 @@ unwinding with _Unwind_Resume. According to ARM EHABI 8.4.1, __cxa_end_cleanup() should not clobber any register, thus we have to write this function in assembly so that we can save {r1, r2, r3}. We don't have to save r0 because it is the return value and the -first argument to _Unwind_Resume(). The function also saves/restores r4 to -keep the stack aligned and to provide a temp register. _Unwind_Resume never -returns and we need to keep the original lr so just branch to it. When -targeting bare metal, the function also clobbers ip/r12 to hold the address of -_Unwind_Resume, which may be too far away for an ordinary branch. +first argument to _Unwind_Resume(). In addition, we are saving r4 in order to +align the stack to 16 bytes, even though it is a callee-save register. */ __attribute__((used)) static _Unwind_Exception * __cxa_end_cleanup_impl() @@ -377,30 +374,20 @@ __cxa_end_cleanup_impl() return &exception_header->unwindHeader; } -asm(" .pushsection .text.__cxa_end_cleanup,\"ax\",%progbits\n" +asm ( + " .pushsection .text.__cxa_end_cleanup,\"ax\",%progbits\n" " .globl __cxa_end_cleanup\n" " .type __cxa_end_cleanup,%function\n" "__cxa_end_cleanup:\n" -#if defined(__ARM_FEATURE_BTI_DEFAULT) - " bti\n" -#endif " push {r1, r2, r3, r4}\n" - " mov r4, lr\n" " bl __cxa_end_cleanup_impl\n" - " mov lr, r4\n" -#if defined(LIBCXXABI_BAREMETAL) - " ldr r4, =_Unwind_Resume\n" - " mov ip, r4\n" -#endif " pop {r1, r2, r3, r4}\n" -#if defined(LIBCXXABI_BAREMETAL) - " bx ip\n" -#else - " b _Unwind_Resume\n" -#endif - " .popsection"); -#endif // defined(_LIBCXXABI_ARM_EHABI) - + " bl _Unwind_Resume\n" + " bl abort\n" + " .popsection" +); +#endif // defined(_LIBCXXABI_ARM_EHABI) + /* This routine can catch foreign or native exceptions. If native, the exception can be a primary or dependent variety. This routine may remain blissfully diff --git a/src/cxa_guard.cpp b/src/cxa_guard.cpp index 64e1e59..f4c2a18 100644 --- a/src/cxa_guard.cpp +++ b/src/cxa_guard.cpp @@ -8,12 +8,11 @@ //===----------------------------------------------------------------------===// #include "__cxxabi_config.h" -#include "cxxabi.h" -// Tell the implementation that we're building the actual implementation -// (and not testing it) -#define BUILDING_CXA_GUARD -#include "cxa_guard_impl.h" +#include "abort_message.h" +#include <__threading_support> + +#include <stdint.h> /* This implementation must be careful to not call code external to this file @@ -25,30 +24,242 @@ to not be a problem. */ -namespace __cxxabiv1 { +namespace __cxxabiv1 +{ + +namespace +{ + +#ifdef __arm__ +// A 32-bit, 4-byte-aligned static data value. The least significant 2 bits must +// be statically initialized to 0. +typedef uint32_t guard_type; -#if defined(_LIBCXXABI_GUARD_ABI_ARM) -using guard_type = uint32_t; +inline void set_initialized(guard_type* guard_object) { + *guard_object |= 1; +} #else -using guard_type = uint64_t; +typedef uint64_t guard_type; + +void set_initialized(guard_type* guard_object) { + char* initialized = (char*)guard_object; + *initialized = 1; +} +#endif + +#if defined(_LIBCXXABI_HAS_NO_THREADS) || (defined(__APPLE__) && !defined(__arm__)) +#ifdef __arm__ + +// Test the lowest bit. +inline bool is_initialized(guard_type* guard_object) { + return (*guard_object) & 1; +} + +#else + +bool is_initialized(guard_type* guard_object) { + char* initialized = (char*)guard_object; + return *initialized; +} + #endif +#endif + +#ifndef _LIBCXXABI_HAS_NO_THREADS +std::__libcpp_mutex_t guard_mut = _LIBCPP_MUTEX_INITIALIZER; +std::__libcpp_condvar_t guard_cv = _LIBCPP_CONDVAR_INITIALIZER; +#endif + +#if defined(__APPLE__) && !defined(__arm__) + +typedef uint32_t lock_type; + +#if __LITTLE_ENDIAN__ + +inline +lock_type +get_lock(uint64_t x) +{ + return static_cast<lock_type>(x >> 32); +} + +inline +void +set_lock(uint64_t& x, lock_type y) +{ + x = static_cast<uint64_t>(y) << 32; +} + +#else // __LITTLE_ENDIAN__ + +inline +lock_type +get_lock(uint64_t x) +{ + return static_cast<lock_type>(x); +} + +inline +void +set_lock(uint64_t& x, lock_type y) +{ + x = y; +} + +#endif // __LITTLE_ENDIAN__ + +#else // !__APPLE__ || __arm__ + +typedef bool lock_type; + +#if !defined(__arm__) +static_assert(std::is_same<guard_type, uint64_t>::value, ""); + +inline lock_type get_lock(uint64_t x) +{ + union + { + uint64_t guard; + uint8_t lock[2]; + } f = {x}; + return f.lock[1] != 0; +} + +inline void set_lock(uint64_t& x, lock_type y) +{ + union + { + uint64_t guard; + uint8_t lock[2]; + } f = {0}; + f.lock[1] = y; + x = f.guard; +} +#else // defined(__arm__) +static_assert(std::is_same<guard_type, uint32_t>::value, ""); + +inline lock_type get_lock(uint32_t x) +{ + union + { + uint32_t guard; + uint8_t lock[2]; + } f = {x}; + return f.lock[1] != 0; +} + +inline void set_lock(uint32_t& x, lock_type y) +{ + union + { + uint32_t guard; + uint8_t lock[2]; + } f = {0}; + f.lock[1] = y; + x = f.guard; +} + +#endif // !defined(__arm__) + +#endif // __APPLE__ && !__arm__ + +} // unnamed namespace extern "C" { -_LIBCXXABI_FUNC_VIS int __cxa_guard_acquire(guard_type* raw_guard_object) { - SelectedImplementation imp(raw_guard_object); - return static_cast<int>(imp.cxa_guard_acquire()); + +#ifndef _LIBCXXABI_HAS_NO_THREADS +_LIBCXXABI_FUNC_VIS int __cxa_guard_acquire(guard_type *guard_object) { + char* initialized = (char*)guard_object; + if (std::__libcpp_mutex_lock(&guard_mut)) + abort_message("__cxa_guard_acquire failed to acquire mutex"); + int result = *initialized == 0; + if (result) + { +#if defined(__APPLE__) && !defined(__arm__) + // This is a special-case pthread dependency for Mac. We can't pull this + // out into libcxx's threading API (__threading_support) because not all + // supported Mac environments provide this function (in pthread.h). To + // make it possible to build/use libcxx in those environments, we have to + // keep this pthread dependency local to libcxxabi. If there is some + // convenient way to detect precisely when pthread_mach_thread_np is + // available in a given Mac environment, it might still be possible to + // bury this dependency in __threading_support. + #ifdef _LIBCPP_HAS_THREAD_API_PTHREAD + const lock_type id = pthread_mach_thread_np(std::__libcpp_thread_get_current_id()); + #else + #error "How do I pthread_mach_thread_np()?" + #endif + lock_type lock = get_lock(*guard_object); + if (lock) + { + // if this thread set lock for this same guard_object, abort + if (lock == id) + abort_message("__cxa_guard_acquire detected deadlock"); + do + { + if (std::__libcpp_condvar_wait(&guard_cv, &guard_mut)) + abort_message("__cxa_guard_acquire condition variable wait failed"); + lock = get_lock(*guard_object); + } while (lock); + result = !is_initialized(guard_object); + if (result) + set_lock(*guard_object, id); + } + else + set_lock(*guard_object, id); +#else // !__APPLE__ || __arm__ + while (get_lock(*guard_object)) + if (std::__libcpp_condvar_wait(&guard_cv, &guard_mut)) + abort_message("__cxa_guard_acquire condition variable wait failed"); + result = *initialized == 0; + if (result) + set_lock(*guard_object, true); +#endif // !__APPLE__ || __arm__ + } + if (std::__libcpp_mutex_unlock(&guard_mut)) + abort_message("__cxa_guard_acquire failed to release mutex"); + return result; +} + +_LIBCXXABI_FUNC_VIS void __cxa_guard_release(guard_type *guard_object) { + if (std::__libcpp_mutex_lock(&guard_mut)) + abort_message("__cxa_guard_release failed to acquire mutex"); + *guard_object = 0; + set_initialized(guard_object); + if (std::__libcpp_mutex_unlock(&guard_mut)) + abort_message("__cxa_guard_release failed to release mutex"); + if (std::__libcpp_condvar_broadcast(&guard_cv)) + abort_message("__cxa_guard_release failed to broadcast condition variable"); +} + +_LIBCXXABI_FUNC_VIS void __cxa_guard_abort(guard_type *guard_object) { + if (std::__libcpp_mutex_lock(&guard_mut)) + abort_message("__cxa_guard_abort failed to acquire mutex"); + *guard_object = 0; + if (std::__libcpp_mutex_unlock(&guard_mut)) + abort_message("__cxa_guard_abort failed to release mutex"); + if (std::__libcpp_condvar_broadcast(&guard_cv)) + abort_message("__cxa_guard_abort failed to broadcast condition variable"); +} + +#else // _LIBCXXABI_HAS_NO_THREADS + +_LIBCXXABI_FUNC_VIS int __cxa_guard_acquire(guard_type *guard_object) { + return !is_initialized(guard_object); } -_LIBCXXABI_FUNC_VIS void __cxa_guard_release(guard_type *raw_guard_object) { - SelectedImplementation imp(raw_guard_object); - imp.cxa_guard_release(); +_LIBCXXABI_FUNC_VIS void __cxa_guard_release(guard_type *guard_object) { + *guard_object = 0; + set_initialized(guard_object); } -_LIBCXXABI_FUNC_VIS void __cxa_guard_abort(guard_type *raw_guard_object) { - SelectedImplementation imp(raw_guard_object); - imp.cxa_guard_abort(); +_LIBCXXABI_FUNC_VIS void __cxa_guard_abort(guard_type *guard_object) { + *guard_object = 0; } + +#endif // !_LIBCXXABI_HAS_NO_THREADS + } // extern "C" } // __cxxabiv1 diff --git a/src/cxa_guard_impl.h b/src/cxa_guard_impl.h deleted file mode 100644 index 552c454..0000000 --- a/src/cxa_guard_impl.h +++ /dev/null @@ -1,568 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// -#ifndef LIBCXXABI_SRC_INCLUDE_CXA_GUARD_IMPL_H -#define LIBCXXABI_SRC_INCLUDE_CXA_GUARD_IMPL_H - -/* cxa_guard_impl.h - Implements the C++ runtime support for function local - * static guards. - * The layout of the guard object is the same across ARM and Itanium. - * - * The first "guard byte" (which is checked by the compiler) is set only upon - * the completion of cxa release. - * - * The second "init byte" does the rest of the bookkeeping. It tracks if - * initialization is complete or pending, and if there are waiting threads. - * - * If the guard variable is 64-bits and the platforms supplies a 32-bit thread - * identifier, it is used to detect recursive initialization. The thread ID of - * the thread currently performing initialization is stored in the second word. - * - * Guard Object Layout: - * ------------------------------------------------------------------------- - * |a: guard byte | a+1: init byte | a+2 : unused ... | a+4: thread-id ... | - * ------------------------------------------------------------------------ - * - * Access Protocol: - * For each implementation the guard byte is checked and set before accessing - * the init byte. - * - * Overall Design: - * The implementation was designed to allow each implementation to be tested - * independent of the C++ runtime or platform support. - * - */ - -#include "__cxxabi_config.h" -#include "include/atomic_support.h" -#include <unistd.h> -#include <sys/types.h> -// Android Trusty: sys/syscall.h tries to include bits/syscall.h, which is -// missing. Trusty seems to define _LIBCXXABI_HAS_NO_THREADS, and gettid isn't -// needed in that case, so skip sys/syscall.h. -#if defined(__has_include) && !defined(_LIBCXXABI_HAS_NO_THREADS) -# if __has_include(<sys/syscall.h>) -# include <sys/syscall.h> -# endif -#endif - -#include <stdlib.h> -#include <__threading_support> - -// To make testing possible, this header is included from both cxa_guard.cpp -// and a number of tests. -// -// For this reason we place everything in an anonymous namespace -- even though -// we're in a header. We want the actual implementation and the tests to have -// unique definitions of the types in this header (since the tests may depend -// on function local statics). -// -// To enforce this either `BUILDING_CXA_GUARD` or `TESTING_CXA_GUARD` must be -// defined when including this file. Only `src/cxa_guard.cpp` should define -// the former. -#ifdef BUILDING_CXA_GUARD -# include "abort_message.h" -# define ABORT_WITH_MESSAGE(...) ::abort_message(__VA_ARGS__) -#elif defined(TESTING_CXA_GUARD) -# define ABORT_WITH_MESSAGE(...) ::abort() -#else -# error "Either BUILDING_CXA_GUARD or TESTING_CXA_GUARD must be defined" -#endif - -#if __has_feature(thread_sanitizer) -extern "C" void __tsan_acquire(void*); -extern "C" void __tsan_release(void*); -#else -#define __tsan_acquire(addr) ((void)0) -#define __tsan_release(addr) ((void)0) -#endif - -namespace __cxxabiv1 { -// Use an anonymous namespace to ensure that the tests and actual implementation -// have unique definitions of these symbols. -namespace { - -//===----------------------------------------------------------------------===// -// Misc Utilities -//===----------------------------------------------------------------------===// - -template <class T, T(*Init)()> -struct LazyValue { - LazyValue() : is_init(false) {} - - T& get() { - if (!is_init) { - value = Init(); - is_init = true; - } - return value; - } - private: - T value; - bool is_init = false; -}; - -//===----------------------------------------------------------------------===// -// PlatformGetThreadID -//===----------------------------------------------------------------------===// - -#if defined(__APPLE__) && defined(_LIBCPP_HAS_THREAD_API_PTHREAD) -uint32_t PlatformThreadID() { - static_assert(sizeof(mach_port_t) == sizeof(uint32_t), ""); - return static_cast<uint32_t>( - pthread_mach_thread_np(std::__libcpp_thread_get_current_id())); -} -#elif defined(SYS_gettid) && defined(_LIBCPP_HAS_THREAD_API_PTHREAD) && \ - !defined(__BIONIC__) -// Bionic: Disable the SYS_gettid feature for now. Some processes on Android -// block SYS_gettid using seccomp. -uint32_t PlatformThreadID() { - static_assert(sizeof(pid_t) == sizeof(uint32_t), ""); - return static_cast<uint32_t>(syscall(SYS_gettid)); -} -#else -constexpr uint32_t (*PlatformThreadID)() = nullptr; -#endif - - -constexpr bool PlatformSupportsThreadID() { -#ifdef __clang__ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wtautological-pointer-compare" -#endif - return +PlatformThreadID != nullptr; -#ifdef __clang__ -#pragma clang diagnostic pop -#endif -} - -//===----------------------------------------------------------------------===// -// GuardBase -//===----------------------------------------------------------------------===// - -enum class AcquireResult { - INIT_IS_DONE, - INIT_IS_PENDING, -}; -constexpr AcquireResult INIT_IS_DONE = AcquireResult::INIT_IS_DONE; -constexpr AcquireResult INIT_IS_PENDING = AcquireResult::INIT_IS_PENDING; - -static constexpr uint8_t UNSET = 0; -static constexpr uint8_t COMPLETE_BIT = (1 << 0); -static constexpr uint8_t PENDING_BIT = (1 << 1); -static constexpr uint8_t WAITING_BIT = (1 << 2); - -template <class Derived> -struct GuardObject { - GuardObject() = delete; - GuardObject(GuardObject const&) = delete; - GuardObject& operator=(GuardObject const&) = delete; - - explicit GuardObject(uint32_t* g) - : base_address(g), guard_byte_address(reinterpret_cast<uint8_t*>(g)), - init_byte_address(reinterpret_cast<uint8_t*>(g) + 1), - thread_id_address(nullptr) {} - - explicit GuardObject(uint64_t* g) - : base_address(g), guard_byte_address(reinterpret_cast<uint8_t*>(g)), - init_byte_address(reinterpret_cast<uint8_t*>(g) + 1), - thread_id_address(reinterpret_cast<uint32_t*>(g) + 1) {} - -public: - /// Implements __cxa_guard_acquire - AcquireResult cxa_guard_acquire() { - AtomicInt<uint8_t> guard_byte(guard_byte_address); - if (guard_byte.load(std::_AO_Acquire) != UNSET) - return INIT_IS_DONE; - return derived()->acquire_init_byte(); - } - - /// Implements __cxa_guard_release - void cxa_guard_release() { - AtomicInt<uint8_t> guard_byte(guard_byte_address); - // Store complete first, so that when release wakes other folks, they see - // it as having been completed. - guard_byte.store(COMPLETE_BIT, std::_AO_Release); - derived()->release_init_byte(); - } - - /// Implements __cxa_guard_abort - void cxa_guard_abort() { derived()->abort_init_byte(); } - -public: - /// base_address - the address of the original guard object. - void* const base_address; - /// The address of the guord byte at offset 0. - uint8_t* const guard_byte_address; - /// The address of the byte used by the implementation during initialization. - uint8_t* const init_byte_address; - /// An optional address storing an identifier for the thread performing initialization. - /// It's used to detect recursive initialization. - uint32_t* const thread_id_address; - -private: - Derived* derived() { return static_cast<Derived*>(this); } -}; - -//===----------------------------------------------------------------------===// -// Single Threaded Implementation -//===----------------------------------------------------------------------===// - -struct InitByteNoThreads : GuardObject<InitByteNoThreads> { - using GuardObject::GuardObject; - - AcquireResult acquire_init_byte() { - if (*init_byte_address == COMPLETE_BIT) - return INIT_IS_DONE; - if (*init_byte_address & PENDING_BIT) - ABORT_WITH_MESSAGE("__cxa_guard_acquire detected recursive initialization"); - *init_byte_address = PENDING_BIT; - return INIT_IS_PENDING; - } - - void release_init_byte() { *init_byte_address = COMPLETE_BIT; } - void abort_init_byte() { *init_byte_address = UNSET; } -}; - - -//===----------------------------------------------------------------------===// -// Global Mutex Implementation -//===----------------------------------------------------------------------===// - -struct LibcppMutex; -struct LibcppCondVar; - -#ifndef _LIBCXXABI_HAS_NO_THREADS -struct LibcppMutex { - LibcppMutex() = default; - LibcppMutex(LibcppMutex const&) = delete; - LibcppMutex& operator=(LibcppMutex const&) = delete; - - bool lock() { return std::__libcpp_mutex_lock(&mutex); } - bool unlock() { return std::__libcpp_mutex_unlock(&mutex); } - -private: - friend struct LibcppCondVar; - std::__libcpp_mutex_t mutex = _LIBCPP_MUTEX_INITIALIZER; -}; - -struct LibcppCondVar { - LibcppCondVar() = default; - LibcppCondVar(LibcppCondVar const&) = delete; - LibcppCondVar& operator=(LibcppCondVar const&) = delete; - - bool wait(LibcppMutex& mut) { - return std::__libcpp_condvar_wait(&cond, &mut.mutex); - } - bool broadcast() { return std::__libcpp_condvar_broadcast(&cond); } - -private: - std::__libcpp_condvar_t cond = _LIBCPP_CONDVAR_INITIALIZER; -}; -#else -struct LibcppMutex {}; -struct LibcppCondVar {}; -#endif // !defined(_LIBCXXABI_HAS_NO_THREADS) - - -template <class Mutex, class CondVar, Mutex& global_mutex, CondVar& global_cond, - uint32_t (*GetThreadID)() = PlatformThreadID> -struct InitByteGlobalMutex - : GuardObject<InitByteGlobalMutex<Mutex, CondVar, global_mutex, global_cond, - GetThreadID>> { - - using BaseT = typename InitByteGlobalMutex::GuardObject; - using BaseT::BaseT; - - explicit InitByteGlobalMutex(uint32_t *g) - : BaseT(g), has_thread_id_support(false) {} - explicit InitByteGlobalMutex(uint64_t *g) - : BaseT(g), has_thread_id_support(PlatformSupportsThreadID()) {} - -public: - AcquireResult acquire_init_byte() { - LockGuard g("__cxa_guard_acquire"); - // Check for possible recursive initialization. - if (has_thread_id_support && (*init_byte_address & PENDING_BIT)) { - if (*thread_id_address == current_thread_id.get()) - ABORT_WITH_MESSAGE("__cxa_guard_acquire detected recursive initialization"); - } - - // Wait until the pending bit is not set. - while (*init_byte_address & PENDING_BIT) { - *init_byte_address |= WAITING_BIT; - global_cond.wait(global_mutex); - } - - if (*init_byte_address == COMPLETE_BIT) - return INIT_IS_DONE; - - if (has_thread_id_support) - *thread_id_address = current_thread_id.get(); - - *init_byte_address = PENDING_BIT; - return INIT_IS_PENDING; - } - - void release_init_byte() { - bool has_waiting; - { - LockGuard g("__cxa_guard_release"); - has_waiting = *init_byte_address & WAITING_BIT; - *init_byte_address = COMPLETE_BIT; - } - if (has_waiting) { - if (global_cond.broadcast()) { - ABORT_WITH_MESSAGE("%s failed to broadcast", "__cxa_guard_release"); - } - } - } - - void abort_init_byte() { - bool has_waiting; - { - LockGuard g("__cxa_guard_abort"); - if (has_thread_id_support) - *thread_id_address = 0; - has_waiting = *init_byte_address & WAITING_BIT; - *init_byte_address = UNSET; - } - if (has_waiting) { - if (global_cond.broadcast()) { - ABORT_WITH_MESSAGE("%s failed to broadcast", "__cxa_guard_abort"); - } - } - } - -private: - using BaseT::init_byte_address; - using BaseT::thread_id_address; - const bool has_thread_id_support; - LazyValue<uint32_t, GetThreadID> current_thread_id; - -private: - struct LockGuard { - LockGuard() = delete; - LockGuard(LockGuard const&) = delete; - LockGuard& operator=(LockGuard const&) = delete; - - explicit LockGuard(const char* calling_func) - : calling_func(calling_func) { - if (global_mutex.lock()) - ABORT_WITH_MESSAGE("%s failed to acquire mutex", calling_func); - } - - ~LockGuard() { - if (global_mutex.unlock()) - ABORT_WITH_MESSAGE("%s failed to release mutex", calling_func); - } - - private: - const char* const calling_func; - }; -}; - -//===----------------------------------------------------------------------===// -// Futex Implementation -//===----------------------------------------------------------------------===// - -#if defined(SYS_futex) -void PlatformFutexWait(int* addr, int expect) { - constexpr int WAIT = 0; - syscall(SYS_futex, addr, WAIT, expect, 0); - __tsan_acquire(addr); -} -void PlatformFutexWake(int* addr) { - constexpr int WAKE = 1; - __tsan_release(addr); - syscall(SYS_futex, addr, WAKE, INT_MAX); -} -#else -constexpr void (*PlatformFutexWait)(int*, int) = nullptr; -constexpr void (*PlatformFutexWake)(int*) = nullptr; -#endif - -constexpr bool PlatformSupportsFutex() { -#ifdef __clang__ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wtautological-pointer-compare" -#endif - return +PlatformFutexWait != nullptr; -#ifdef __clang__ -#pragma clang diagnostic pop -#endif -} - -/// InitByteFutex - Manages initialization using atomics and the futex syscall -/// for waiting and waking. -template <void (*Wait)(int*, int) = PlatformFutexWait, - void (*Wake)(int*) = PlatformFutexWake, - uint32_t (*GetThreadIDArg)() = PlatformThreadID> -struct InitByteFutex : GuardObject<InitByteFutex<Wait, Wake, GetThreadIDArg>> { - using BaseT = typename InitByteFutex::GuardObject; - - /// ARM Constructor - explicit InitByteFutex(uint32_t *g) : BaseT(g), - init_byte(this->init_byte_address), - has_thread_id_support(this->thread_id_address && GetThreadIDArg), - thread_id(this->thread_id_address) {} - - /// Itanium Constructor - explicit InitByteFutex(uint64_t *g) : BaseT(g), - init_byte(this->init_byte_address), - has_thread_id_support(this->thread_id_address && GetThreadIDArg), - thread_id(this->thread_id_address) {} - -public: - AcquireResult acquire_init_byte() { - while (true) { - uint8_t last_val = UNSET; - if (init_byte.compare_exchange(&last_val, PENDING_BIT, std::_AO_Acq_Rel, - std::_AO_Acquire)) { - if (has_thread_id_support) { - thread_id.store(current_thread_id.get(), std::_AO_Relaxed); - } - return INIT_IS_PENDING; - } - - if (last_val == COMPLETE_BIT) - return INIT_IS_DONE; - - if (last_val & PENDING_BIT) { - - // Check for recursive initialization - if (has_thread_id_support && thread_id.load(std::_AO_Relaxed) == current_thread_id.get()) { - ABORT_WITH_MESSAGE("__cxa_guard_acquire detected recursive initialization"); - } - - if ((last_val & WAITING_BIT) == 0) { - // This compare exchange can fail for several reasons - // (1) another thread finished the whole thing before we got here - // (2) another thread set the waiting bit we were trying to thread - // (3) another thread had an exception and failed to finish - if (!init_byte.compare_exchange(&last_val, PENDING_BIT | WAITING_BIT, - std::_AO_Acq_Rel, std::_AO_Release)) { - // (1) success, via someone else's work! - if (last_val == COMPLETE_BIT) - return INIT_IS_DONE; - - // (3) someone else, bailed on doing the work, retry from the start! - if (last_val == UNSET) - continue; - - // (2) the waiting bit got set, so we are happy to keep waiting - } - } - wait_on_initialization(); - } - } - } - - void release_init_byte() { - uint8_t old = init_byte.exchange(COMPLETE_BIT, std::_AO_Acq_Rel); - if (old & WAITING_BIT) - wake_all(); - } - - void abort_init_byte() { - if (has_thread_id_support) - thread_id.store(0, std::_AO_Relaxed); - - uint8_t old = init_byte.exchange(0, std::_AO_Acq_Rel); - if (old & WAITING_BIT) - wake_all(); - } - -private: - /// Use the futex to wait on the current guard variable. Futex expects a - /// 32-bit 4-byte aligned address as the first argument, so we have to use use - /// the base address of the guard variable (not the init byte). - void wait_on_initialization() { - Wait(static_cast<int*>(this->base_address), - expected_value_for_futex(PENDING_BIT | WAITING_BIT)); - } - void wake_all() { Wake(static_cast<int*>(this->base_address)); } - -private: - AtomicInt<uint8_t> init_byte; - - const bool has_thread_id_support; - // Unsafe to use unless has_thread_id_support - AtomicInt<uint32_t> thread_id; - LazyValue<uint32_t, GetThreadIDArg> current_thread_id; - - /// Create the expected integer value for futex `wait(int* addr, int expected)`. - /// We pass the base address as the first argument, So this function creates - /// an zero-initialized integer with `b` copied at the correct offset. - static int expected_value_for_futex(uint8_t b) { - int dest_val = 0; - std::memcpy(reinterpret_cast<char*>(&dest_val) + 1, &b, 1); - return dest_val; - } - - static_assert(Wait != nullptr && Wake != nullptr, ""); -}; - -//===----------------------------------------------------------------------===// -// -//===----------------------------------------------------------------------===// - -template <class T> -struct GlobalStatic { - static T instance; -}; -template <class T> -_LIBCPP_SAFE_STATIC T GlobalStatic<T>::instance = {}; - -enum class Implementation { - NoThreads, - GlobalLock, - Futex -}; - -template <Implementation Impl> -struct SelectImplementation; - -template <> -struct SelectImplementation<Implementation::NoThreads> { - using type = InitByteNoThreads; -}; - -template <> -struct SelectImplementation<Implementation::GlobalLock> { - using type = InitByteGlobalMutex< - LibcppMutex, LibcppCondVar, GlobalStatic<LibcppMutex>::instance, - GlobalStatic<LibcppCondVar>::instance, PlatformThreadID>; -}; - -template <> -struct SelectImplementation<Implementation::Futex> { - using type = - InitByteFutex<PlatformFutexWait, PlatformFutexWake, PlatformThreadID>; -}; - -// TODO(EricWF): We should prefer the futex implementation when available. But -// it should be done in a separate step from adding the implementation. -constexpr Implementation CurrentImplementation = -#if defined(_LIBCXXABI_HAS_NO_THREADS) - Implementation::NoThreads; -#elif defined(_LIBCXXABI_USE_FUTEX) - Implementation::Futex; -#else - Implementation::GlobalLock; -#endif - -static_assert(CurrentImplementation != Implementation::Futex - || PlatformSupportsFutex(), "Futex selected but not supported"); - -using SelectedImplementation = - SelectImplementation<CurrentImplementation>::type; - -} // end namespace -} // end namespace __cxxabiv1 - -#endif // LIBCXXABI_SRC_INCLUDE_CXA_GUARD_IMPL_H diff --git a/src/include/atomic_support.h b/src/include/atomic_support.h index c9f8a5a..96dbd2c 100644 --- a/src/include/atomic_support.h +++ b/src/include/atomic_support.h @@ -151,7 +151,7 @@ _ValueType __libcpp_atomic_add(_ValueType* __val, _AddType __a, template <class _ValueType> inline _LIBCPP_INLINE_VISIBILITY _ValueType __libcpp_atomic_exchange(_ValueType* __target, - _ValueType __value, int = _AO_Seq) + _ValueType __value, int __order = _AO_Seq) { _ValueType old = *__target; *__target = __value; @@ -178,34 +178,4 @@ bool __libcpp_atomic_compare_exchange(_ValueType* __val, _LIBCPP_END_NAMESPACE_STD -namespace { - -template <class IntType> -class AtomicInt { -public: - using MemoryOrder = std::__libcpp_atomic_order; - - explicit AtomicInt(IntType *b) : b(b) {} - AtomicInt(AtomicInt const&) = delete; - AtomicInt& operator=(AtomicInt const&) = delete; - - IntType load(MemoryOrder ord) { - return std::__libcpp_atomic_load(b, ord); - } - void store(IntType val, MemoryOrder ord) { - std::__libcpp_atomic_store(b, val, ord); - } - IntType exchange(IntType new_val, MemoryOrder ord) { - return std::__libcpp_atomic_exchange(b, new_val, ord); - } - bool compare_exchange(IntType *expected, IntType desired, MemoryOrder ord_success, MemoryOrder ord_failure) { - return std::__libcpp_atomic_compare_exchange(b, expected, desired, ord_success, ord_failure); - } - -private: - IntType *b; -}; - -} // end namespace - #endif // ATOMIC_SUPPORT_H diff --git a/test/guard_test_basic.pass.cpp b/test/guard_test_basic.pass.cpp deleted file mode 100644 index b0dd41f..0000000 --- a/test/guard_test_basic.pass.cpp +++ /dev/null @@ -1,154 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// -// -// UNSUPPORTED: c++98, c++03 - -#define TESTING_CXA_GUARD -#include "../src/cxa_guard_impl.h" - -using namespace __cxxabiv1; - -template <class GuardType, class Impl> -struct Tests { -private: - Tests() : g{}, impl(&g) {} - GuardType g; - Impl impl; - - uint8_t first_byte() { - uint8_t first; - std::memcpy(&first, &g, 1); - return first; - } - - void reset() { g = {}; } - -public: - // Test the post conditions on cxa_guard_acquire, cxa_guard_abort, and - // cxa_guard_release. Specifically, that they leave the first byte with - // the value 0 or 1 as specified by the ARM or Itanium specification. - static void test() { - Tests tests; - tests.test_acquire(); - tests.test_abort(); - tests.test_release(); - } - - void test_acquire() { - { - reset(); - assert(first_byte() == 0); - assert(impl.cxa_guard_acquire() == INIT_IS_PENDING); - assert(first_byte() == 0); - } - { - reset(); - assert(first_byte() == 0); - assert(impl.cxa_guard_acquire() == INIT_IS_PENDING); - impl.cxa_guard_release(); - assert(first_byte() == 1); - assert(impl.cxa_guard_acquire() == INIT_IS_DONE); - } - } - - void test_release() { - { - reset(); - assert(first_byte() == 0); - assert(impl.cxa_guard_acquire() == INIT_IS_PENDING); - assert(first_byte() == 0); - impl.cxa_guard_release(); - assert(first_byte() == 1); - } - } - - void test_abort() { - { - reset(); - assert(first_byte() == 0); - assert(impl.cxa_guard_acquire() == INIT_IS_PENDING); - assert(first_byte() == 0); - impl.cxa_guard_abort(); - assert(first_byte() == 0); - assert(impl.cxa_guard_acquire() == INIT_IS_PENDING); - assert(first_byte() == 0); - } - } -}; - -struct NopMutex { - bool lock() { - assert(!is_locked); - is_locked = true; - return false; - } - bool unlock() { - assert(is_locked); - is_locked = false; - return false; - } - -private: - bool is_locked = false; -}; -static NopMutex global_nop_mutex = {}; - -struct NopCondVar { - bool broadcast() { return false; } - bool wait(NopMutex&) { return false; } -}; -static NopCondVar global_nop_cond = {}; - -void NopFutexWait(int*, int) { assert(false); } -void NopFutexWake(int*) { assert(false); } -uint32_t MockGetThreadID() { return 0; } - -int main() { - { -#if defined(_LIBCXXABI_HAS_NO_THREADS) - static_assert(CurrentImplementation == Implementation::NoThreads, ""); - static_assert( - std::is_same<SelectedImplementation, InitByteNoThreads>::value, ""); -#else - static_assert(CurrentImplementation == Implementation::GlobalLock, ""); - static_assert( - std::is_same< - SelectedImplementation, - InitByteGlobalMutex<LibcppMutex, LibcppCondVar, - GlobalStatic<LibcppMutex>::instance, - GlobalStatic<LibcppCondVar>::instance>>::value, - ""); -#endif - } - { -#if defined(__APPLE__) || defined(__linux__) - assert(PlatformThreadID); -#endif - if (PlatformSupportsThreadID()) { - assert(PlatformThreadID() != 0); - assert(PlatformThreadID() == PlatformThreadID()); - } - } - { - Tests<uint32_t, InitByteNoThreads>::test(); - Tests<uint64_t, InitByteNoThreads>::test(); - } - { - using MutexImpl = - InitByteGlobalMutex<NopMutex, NopCondVar, global_nop_mutex, - global_nop_cond, MockGetThreadID>; - Tests<uint32_t, MutexImpl>::test(); - Tests<uint64_t, MutexImpl>::test(); - } - { - using FutexImpl = - InitByteFutex<&NopFutexWait, &NopFutexWake, &MockGetThreadID>; - Tests<uint32_t, FutexImpl>::test(); - Tests<uint64_t, FutexImpl>::test(); - } -} diff --git a/test/guard_threaded_test.pass.cpp b/test/guard_threaded_test.pass.cpp deleted file mode 100644 index e46af56..0000000 --- a/test/guard_threaded_test.pass.cpp +++ /dev/null @@ -1,419 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -// UNSUPPORTED: c++98, c++03 -// UNSUPPORTED: libcxxabi-no-threads, libcxxabi-no-exceptions - -#define TESTING_CXA_GUARD -#include "../src/cxa_guard_impl.h" -#include <unordered_map> -#include <thread> -#include <atomic> -#include <array> -#include <cassert> -#include <memory> -#include <vector> - - -using namespace __cxxabiv1; - -enum class InitResult { - COMPLETE, - PERFORMED, - WAITED, - ABORTED -}; -constexpr InitResult COMPLETE = InitResult::COMPLETE; -constexpr InitResult PERFORMED = InitResult::PERFORMED; -constexpr InitResult WAITED = InitResult::WAITED; -constexpr InitResult ABORTED = InitResult::ABORTED; - - -template <class Impl, class GuardType, class Init> -InitResult check_guard(GuardType *g, Init init) { - uint8_t *first_byte = reinterpret_cast<uint8_t*>(g); - if (std::__libcpp_atomic_load(first_byte, std::_AO_Acquire) == 0) { - Impl impl(g); - if (impl.cxa_guard_acquire() == INIT_IS_PENDING) { -#ifndef LIBCXXABI_HAS_NO_EXCEPTIONS - try { -#endif - init(); - impl.cxa_guard_release(); - return PERFORMED; -#ifndef LIBCXXABI_HAS_NO_EXCEPTIONS - } catch (...) { - impl.cxa_guard_abort(); - return ABORTED; - } -#endif - } - return WAITED; - } - return COMPLETE; -} - - -template <class GuardType, class Impl> -struct FunctionLocalStatic { - FunctionLocalStatic() { reset(); } - FunctionLocalStatic(FunctionLocalStatic const&) = delete; - - template <class InitFunc> - InitResult access(InitFunc&& init) { - ++waiting_threads; - auto res = check_guard<Impl>(&guard_object, init); - --waiting_threads; - ++result_counts[static_cast<int>(res)]; - return res; - } - - struct Accessor { - explicit Accessor(FunctionLocalStatic& obj) : this_obj(&obj) {} - - template <class InitFn> - void operator()(InitFn && fn) const { - this_obj->access(std::forward<InitFn>(fn)); - } - private: - FunctionLocalStatic *this_obj; - }; - - Accessor get_access() { - return Accessor(*this); - } - - void reset() { - guard_object = 0; - waiting_threads.store(0); - for (auto& counter : result_counts) { - counter.store(0); - } - } - - int get_count(InitResult I) const { - return result_counts[static_cast<int>(I)].load(); - } - int num_completed() const { - return get_count(COMPLETE) + get_count(PERFORMED) + get_count(WAITED); - } - int num_waiting() const { - return waiting_threads.load(); - } - -private: - GuardType guard_object; - std::atomic<int> waiting_threads; - std::array<std::atomic<int>, 4> result_counts; - static_assert(static_cast<int>(ABORTED) == 3, "only 4 result kinds expected"); -}; - -struct ThreadGroup { - ThreadGroup() = default; - ThreadGroup(ThreadGroup const&) = delete; - - template <class ...Args> - void Create(Args&& ...args) { - threads.emplace_back(std::forward<Args>(args)...); - } - - void JoinAll() { - for (auto& t : threads) { - t.join(); - } - } - -private: - std::vector<std::thread> threads; -}; - -struct Barrier { - explicit Barrier(int n) : m_wait_for(n) { reset(); } - Barrier(Barrier const&) = delete; - - void wait() { - ++m_entered; - while (m_entered.load() < m_wait_for) { - std::this_thread::yield(); - } - assert(m_entered.load() == m_wait_for); - ++m_exited; - } - - int num_waiting() const { - return m_entered.load() - m_exited.load(); - } - - void reset() { - m_entered.store(0); - m_exited.store(0); - } -private: - const int m_wait_for; - std::atomic<int> m_entered; - std::atomic<int> m_exited; -}; - -struct Notification { - Notification() { reset(); } - Notification(Notification const&) = delete; - - int num_waiting() const { - return m_waiting.load(); - } - - void wait() { - if (m_cond.load()) - return; - ++m_waiting; - while (!m_cond.load()) { - std::this_thread::yield(); - } - --m_waiting; - } - - void notify() { - m_cond.store(true); - } - - template <class Cond> - void notify_when(Cond &&c) { - if (m_cond.load()) - return; - while (!c()) { - std::this_thread::yield(); - } - m_cond.store(true); - } - - void reset() { - m_cond.store(0); - m_waiting.store(0); - } -private: - std::atomic<bool> m_cond; - std::atomic<int> m_waiting; -}; - - -template <class GuardType, class Impl> -void test_free_for_all() { - const int num_waiting_threads = 10; // one initializing thread, 10 waiters. - - FunctionLocalStatic<GuardType, Impl> test_obj; - - Barrier start_init_barrier(num_waiting_threads); - bool already_init = false; - ThreadGroup threads; - for (int i=0; i < num_waiting_threads; ++i) { - threads.Create([&]() { - start_init_barrier.wait(); - test_obj.access([&]() { - assert(!already_init); - already_init = true; - }); - }); - } - - // wait for the other threads to finish initialization. - threads.JoinAll(); - - assert(test_obj.get_count(PERFORMED) == 1); - assert(test_obj.get_count(COMPLETE) + test_obj.get_count(WAITED) == 9); -} - -template <class GuardType, class Impl> -void test_waiting_for_init() { - const int num_waiting_threads = 10; // one initializing thread, 10 waiters. - - Notification init_pending; - Notification init_barrier; - FunctionLocalStatic<GuardType, Impl> test_obj; - auto access_fn = test_obj.get_access(); - - ThreadGroup threads; - threads.Create(access_fn, - [&]() { - init_pending.notify(); - init_barrier.wait(); - } - ); - init_pending.wait(); - - assert(test_obj.num_waiting() == 1); - - for (int i=0; i < num_waiting_threads; ++i) { - threads.Create(access_fn, []() { assert(false); }); - } - // unblock the initializing thread - init_barrier.notify_when([&]() { - return test_obj.num_waiting() == num_waiting_threads + 1; - }); - - // wait for the other threads to finish initialization. - threads.JoinAll(); - - assert(test_obj.get_count(PERFORMED) == 1); - assert(test_obj.get_count(WAITED) == 10); - assert(test_obj.get_count(COMPLETE) == 0); -} - - -template <class GuardType, class Impl> -void test_aborted_init() { - const int num_waiting_threads = 10; // one initializing thread, 10 waiters. - - Notification init_pending; - Notification init_barrier; - FunctionLocalStatic<GuardType, Impl> test_obj; - auto access_fn = test_obj.get_access(); - - ThreadGroup threads; - threads.Create(access_fn, - [&]() { - init_pending.notify(); - init_barrier.wait(); - throw 42; - } - ); - init_pending.wait(); - - assert(test_obj.num_waiting() == 1); - - bool already_init = false; - for (int i=0; i < num_waiting_threads; ++i) { - threads.Create(access_fn, [&]() { - assert(!already_init); - already_init = true; - }); - } - // unblock the initializing thread - init_barrier.notify_when([&]() { - return test_obj.num_waiting() == num_waiting_threads + 1; - }); - - // wait for the other threads to finish initialization. - threads.JoinAll(); - - assert(test_obj.get_count(ABORTED) == 1); - assert(test_obj.get_count(PERFORMED) == 1); - assert(test_obj.get_count(WAITED) == 9); - assert(test_obj.get_count(COMPLETE) == 0); -} - - -template <class GuardType, class Impl> -void test_completed_init() { - const int num_waiting_threads = 10; // one initializing thread, 10 waiters. - - Notification init_barrier; - FunctionLocalStatic<GuardType, Impl> test_obj; - - test_obj.access([]() {}); - assert(test_obj.num_waiting() == 0); - assert(test_obj.num_completed() == 1); - assert(test_obj.get_count(PERFORMED) == 1); - - auto access_fn = test_obj.get_access(); - ThreadGroup threads; - for (int i=0; i < num_waiting_threads; ++i) { - threads.Create(access_fn, []() { - assert(false); - }); - } - - // wait for the other threads to finish initialization. - threads.JoinAll(); - - assert(test_obj.get_count(ABORTED) == 0); - assert(test_obj.get_count(PERFORMED) == 1); - assert(test_obj.get_count(WAITED) == 0); - assert(test_obj.get_count(COMPLETE) == 10); -} - -template <class Impl> -void test_impl() { - { - test_free_for_all<uint32_t, Impl>(); - test_free_for_all<uint32_t, Impl>(); - } - { - test_waiting_for_init<uint32_t, Impl>(); - test_waiting_for_init<uint64_t, Impl>(); - } - { - test_aborted_init<uint32_t, Impl>(); - test_aborted_init<uint64_t, Impl>(); - } - { - test_completed_init<uint32_t, Impl>(); - test_completed_init<uint64_t, Impl>(); - } -} - -void test_all_impls() { - using MutexImpl = SelectImplementation<Implementation::GlobalLock>::type; - - // Attempt to test the Futex based implementation if it's supported on the - // target platform. - using RealFutexImpl = SelectImplementation<Implementation::Futex>::type; - using FutexImpl = typename std::conditional< - PlatformSupportsFutex(), - RealFutexImpl, - MutexImpl - >::type; - - // Run each test 5 times to help TSAN catch bugs. - const int num_runs = 5; - for (int i=0; i < num_runs; ++i) { - test_impl<MutexImpl>(); - if (PlatformSupportsFutex()) - test_impl<FutexImpl>(); - } -} - -// A dummy -template <bool Dummy = true> -void test_futex_syscall() { - if (!PlatformSupportsFutex()) - return; - int lock1 = 0; - int lock2 = 0; - int lock3 = 0; - std::thread waiter1([&]() { - int expect = 0; - PlatformFutexWait(&lock1, expect); - assert(lock1 == 1); - }); - std::thread waiter2([&]() { - int expect = 0; - PlatformFutexWait(&lock2, expect); - assert(lock2 == 2); - }); - std::thread waiter3([&]() { - int expect = 42; // not the value - PlatformFutexWait(&lock3, expect); // doesn't block - }); - std::thread waker([&]() { - lock1 = 1; - PlatformFutexWake(&lock1); - lock2 = 2; - PlatformFutexWake(&lock2); - }); - waiter1.join(); - waiter2.join(); - waiter3.join(); - waker.join(); -} - -int main() { - // Test each multi-threaded implementation with real threads. - test_all_impls(); - // Test the basic sanity of the futex syscall wrappers. - test_futex_syscall(); -} |