Index: src/cxa_thread_atexit.cpp =================================================================== --- src/cxa_thread_atexit.cpp +++ src/cxa_thread_atexit.cpp @@ -7,20 +7,133 @@ // //===----------------------------------------------------------------------===// +#include "abort_message.h" #include "cxxabi.h" +#include +#include namespace __cxxabiv1 { -extern "C" { -#ifdef HAVE___CXA_THREAD_ATEXIT_IMPL + using Dtor = void(*)(void*); -_LIBCXXABI_FUNC_VIS int __cxa_thread_atexit(void (*dtor)(void *), void *obj, - void *dso_symbol) throw() { - extern int __cxa_thread_atexit_impl(void (*)(void *), void *, void *); - return __cxa_thread_atexit_impl(dtor, obj, dso_symbol); -} + extern "C" +#ifndef HAVE___CXA_THREAD_ATEXIT_IMPL + // A weak symbol is used to detect this function's presence in the C library + // at runtime, even if libc++ is built against an older libc + __attribute__((__weak__)) +#endif + int __cxa_thread_atexit_impl(Dtor, void*, void*); + +#ifndef HAVE___CXA_THREAD_ATEXIT_IMPL + +namespace { + // This implementation is used if the C library does not provide + // __cxa_thread_atexit_impl() for us. It has a number of limitations that are + // difficult to impossible to address without ..._impl(): + // + // - dso_symbol is ignored. This means that a shared library may be unloaded + // (via dlclose()) before its thread_local destructors have run. + // + // - thread_local destructors for the main thread are run by the destructor of + // a static object. This is later than expected; they should run before the + // destructors of any objects with static storage duration. + // + // - thread_local destructors on non-main threads run on the first iteration + // through the pthread_key destructors. std::notify_all_at_thread_exit() + // and similar functions must be careful to wait until the second iteration + // to provide their intended ordering guarantees. + // + // Another limitation, though one shared with ..._impl(), is that any + // thread_locals that are first initialized after non-thread_local global + // destructors begin to run will not be destroyed. [basic.start.term] states + // that all thread_local destructors are sequenced before the destruction of + // objects with static storage duration, resulting in a contradiction if a + // thread_local is constructed after that point. Thus we consider such + // programs ill-formed, and don't bother to run those destructors. (If the + // program terminates abnormally after such a thread_local is constructed, + // the destructor is not expected to run and thus there is no contradiction. + // So construction still has to work.) + + struct DtorList { + Dtor dtor; + void* obj; + DtorList* next; + }; + + // The linked list of thread-local destructors to run + __thread DtorList* dtors = nullptr; + // True if the destructors are currently scheduled to run on this thread + __thread bool dtors_alive = false; + // Used to trigger destructors on thread exit; value is ignored + pthread_key_t dtors_key; + + void run_dtors(void*) { + while (auto head = dtors) { + dtors = head->next; + head->dtor(head->obj); + std::free(head); + } + + dtors_alive = false; + } + + struct DtorsManager { + DtorsManager() { + // There is intentionally no matching pthread_key_delete call, as + // __cxa_thread_atexit() may be called arbitrarily late (for example, from + // global destructors or atexit() handlers). + if (pthread_key_create(&dtors_key, run_dtors) != 0) { + abort_message("pthread_key_create() failed in __cxa_thread_atexit()"); + } + } + + ~DtorsManager() { + // pthread_key destructors do not run on threads that call exit() + // (including when the main thread returns from main()), so we explicitly + // call the destructor here. This runs at exit time (potentially earlier + // if libc++abi is dlclose()'d). Any thread_locals initialized after this + // point will not be destroyed. + run_dtors(nullptr); + } + }; +} // namespace #endif // HAVE__CXA_THREAD_ATEXIT_IMPL +extern "C" { + + _LIBCXXABI_FUNC_VIS int __cxa_thread_atexit(Dtor dtor, void* obj, void* dso_symbol) throw() { +#ifdef HAVE___CXA_THREAD_ATEXIT_IMPL + return __cxa_thread_atexit_impl(dtor, obj, dso_symbol); +#else + if (__cxa_thread_atexit_impl) { + return __cxa_thread_atexit_impl(dtor, obj, dso_symbol); + } else { + // Initialize the dtors pthread_key (uses __cxa_guard_*() for one-time + // initialization and __cxa_atexit() for destruction) + static DtorsManager manager; + + if (!dtors_alive) { + if (pthread_setspecific(dtors_key, &dtors_key) != 0) { + return -1; + } + dtors_alive = true; + } + + auto head = static_cast(std::malloc(sizeof(DtorList))); + if (!head) { + return -1; + } + + head->dtor = dtor; + head->obj = obj; + head->next = dtors; + dtors = head; + + return 0; + } +#endif // HAVE___CXA_THREAD_ATEXIT_IMPL + } + } // extern "C" } // namespace __cxxabiv1 Index: test/CMakeLists.txt =================================================================== --- test/CMakeLists.txt +++ test/CMakeLists.txt @@ -16,7 +16,6 @@ pythonize_bool(LIBCXXABI_ENABLE_THREADS) pythonize_bool(LIBCXXABI_ENABLE_EXCEPTIONS) pythonize_bool(LIBCXXABI_USE_LLVM_UNWINDER) -pythonize_bool(LIBCXXABI_HAS_CXA_THREAD_ATEXIT_IMPL) set(LIBCXXABI_TARGET_INFO "libcxx.test.target_info.LocalTI" CACHE STRING "TargetInfo to use when setting up test environment.") set(LIBCXXABI_EXECUTOR "None" CACHE STRING Index: test/cxa_thread_atexit_test.pass.cpp =================================================================== --- test/cxa_thread_atexit_test.pass.cpp +++ test/cxa_thread_atexit_test.pass.cpp @@ -8,7 +8,6 @@ //===----------------------------------------------------------------------===// // REQUIRES: linux -// REQUIRES: thread_atexit #include #include Index: test/libcxxabi/test/config.py =================================================================== --- test/libcxxabi/test/config.py +++ test/libcxxabi/test/config.py @@ -37,8 +37,6 @@ super(Configuration, self).configure_features() if not self.get_lit_bool('enable_exceptions', True): self.config.available_features.add('libcxxabi-no-exceptions') - if self.get_lit_bool('thread_atexit', True): - self.config.available_features.add('thread_atexit') def configure_compile_flags(self): self.cxx.compile_flags += ['-DLIBCXXABI_NO_TIMER'] Index: test/lit.site.cfg.in =================================================================== --- test/lit.site.cfg.in +++ test/lit.site.cfg.in @@ -13,7 +13,6 @@ config.enable_32bit = "@LIBCXXABI_BUILD_32_BITS@" config.target_info = "@LIBCXXABI_TARGET_INFO@" config.executor = "@LIBCXXABI_EXECUTOR@" -config.thread_atexit = "@LIBCXXABI_HAS_CXA_THREAD_ATEXIT_IMPL@" config.libcxxabi_shared = "@LIBCXXABI_ENABLE_SHARED@" config.enable_shared = "@LIBCXX_ENABLE_SHARED@" config.enable_exceptions = "@LIBCXXABI_ENABLE_EXCEPTIONS@" Index: test/thread_local_destruction_order.pass.cpp =================================================================== --- test/thread_local_destruction_order.pass.cpp +++ test/thread_local_destruction_order.pass.cpp @@ -0,0 +1,56 @@ +//===-------------- thread_local_destruction_order.pass.cpp ---------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is dual licensed under the MIT and the University of Illinois Open +// Source Licenses. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++98, c++03 + +#include +#include + +int seq = 0; + +class OrderChecker { +public: + explicit OrderChecker(int n) : n_{n} { } + + ~OrderChecker() { + assert(seq++ == n_); + } + +private: + int n_; +}; + +template +class CreatesThreadLocalInDestructor { +public: + ~CreatesThreadLocalInDestructor() { + thread_local OrderChecker checker{ID}; + } +}; + +OrderChecker global{7}; + +void thread_fn() { + static OrderChecker fn_static{5}; + thread_local CreatesThreadLocalInDestructor<2> creates_tl2; + thread_local OrderChecker fn_thread_local{1}; + thread_local CreatesThreadLocalInDestructor<0> creates_tl0; +} + +int main() { + static OrderChecker fn_static{6}; + + std::thread{thread_fn}.join(); + assert(seq == 3); + + thread_local OrderChecker fn_thread_local{4}; + thread_local CreatesThreadLocalInDestructor<3> creates_tl; + + return 0; +}