From 5b031b9b56c63fbf2a24638e6ccb8da45ce936c1 Mon Sep 17 00:00:00 2001 From: Jason Merrill Date: Mon, 8 Oct 2012 10:45:24 -0400 Subject: [PATCH] Support C++11 thread_local destructors. gcc/cp/ * decl.c (get_thread_atexit_node): New. (register_dtor_fn): Use it for TLS. libstdc++-v3/ * libsupc++/cxxabi.h: Declare __cxa_thread_atexit. * libsupc++/atexit_thread.cc: New. * libsupc++/Makefile.am (nested_exception.lo): Add it. * config/abi/pre/gnu.ver: Add __cxa_thread_atexit. From-SVN: r192210 --- gcc/cp/ChangeLog | 3 + gcc/cp/decl.c | 119 ++++++++++++-------- gcc/testsuite/ChangeLog | 5 + gcc/testsuite/g++.dg/tls/thread_local3.C | 37 +++++++ gcc/testsuite/g++.dg/tls/thread_local4.C | 47 ++++++++ gcc/testsuite/g++.dg/tls/thread_local5.C | 47 ++++++++ gcc/testsuite/g++.dg/tls/thread_local6.C | 33 ++++++ libstdc++-v3/ChangeLog | 7 ++ libstdc++-v3/config/abi/pre/gnu.ver | 4 + libstdc++-v3/libsupc++/Makefile.am | 6 + libstdc++-v3/libsupc++/Makefile.in | 8 +- libstdc++-v3/libsupc++/atexit_thread.cc | 135 +++++++++++++++++++++++ libstdc++-v3/libsupc++/cxxabi.h | 4 + 13 files changed, 409 insertions(+), 46 deletions(-) create mode 100644 gcc/testsuite/g++.dg/tls/thread_local3.C create mode 100644 gcc/testsuite/g++.dg/tls/thread_local4.C create mode 100644 gcc/testsuite/g++.dg/tls/thread_local5.C create mode 100644 gcc/testsuite/g++.dg/tls/thread_local6.C create mode 100644 libstdc++-v3/libsupc++/atexit_thread.cc diff --git a/gcc/cp/ChangeLog b/gcc/cp/ChangeLog index 871dbaa8530..1807f5b0e43 100644 --- a/gcc/cp/ChangeLog +++ b/gcc/cp/ChangeLog @@ -1,5 +1,8 @@ 2012-10-08 Jason Merrill + * decl.c (get_thread_atexit_node): New. + (register_dtor_fn): Use it for TLS. + Partial implementation of C++11 thread_local. * decl.c (cp_finish_decl): Remove errors about non-trivial initialization and destruction of TLS variables. diff --git a/gcc/cp/decl.c b/gcc/cp/decl.c index b409c34eee1..7dc13fb94a3 100644 --- a/gcc/cp/decl.c +++ b/gcc/cp/decl.c @@ -6575,6 +6575,24 @@ get_atexit_node (void) return atexit_node; } +/* Like get_atexit_node, but for thread-local cleanups. */ + +static tree +get_thread_atexit_node (void) +{ + /* The declaration for `__cxa_thread_atexit' is: + + int __cxa_thread_atexit (void (*)(void *), void *, void *) */ + tree fn_type = build_function_type_list (integer_type_node, + get_atexit_fn_ptr_type (), + ptr_type_node, ptr_type_node, + NULL_TREE); + + /* Now, build the function declaration. */ + tree atexit_fndecl = build_library_fn_ptr ("__cxa_thread_atexit", fn_type); + return decay_conversion (atexit_fndecl, tf_warning_or_error); +} + /* Returns the __dso_handle VAR_DECL. */ static tree @@ -6666,23 +6684,27 @@ tree register_dtor_fn (tree decl) { tree cleanup; + tree addr; tree compound_stmt; tree fcall; tree type; - bool use_dtor; - tree arg0, arg1 = NULL_TREE, arg2 = NULL_TREE; + bool ob_parm, dso_parm, use_dtor; + tree arg0, arg1, arg2; + tree atex_node; type = TREE_TYPE (decl); if (TYPE_HAS_TRIVIAL_DESTRUCTOR (type)) return void_zero_node; - /* If we're using "__cxa_atexit" (or "__aeabi_atexit"), and DECL is - a class object, we can just pass the destructor to - "__cxa_atexit"; we don't have to build a temporary function to do - the cleanup. */ - use_dtor = (flag_use_cxa_atexit - && !targetm.cxx.use_atexit_for_cxa_atexit () - && CLASS_TYPE_P (type)); + /* If we're using "__cxa_atexit" (or "__cxa_thread_atexit" or + "__aeabi_atexit"), and DECL is a class object, we can just pass the + destructor to "__cxa_atexit"; we don't have to build a temporary + function to do the cleanup. */ + ob_parm = (DECL_THREAD_LOCAL_P (decl) + || (flag_use_cxa_atexit + && !targetm.cxx.use_atexit_for_cxa_atexit ())); + dso_parm = ob_parm; + use_dtor = ob_parm && CLASS_TYPE_P (type); if (use_dtor) { int idx; @@ -6720,44 +6742,48 @@ register_dtor_fn (tree decl) end_cleanup_fn (); } - if (DECL_THREAD_LOCAL_P (decl)) - /* We don't have a thread-local atexit yet. FIXME write one using - pthread_key_create and friends. */ - sorry ("thread-local variable %q#D with non-trivial " - "destructor", decl); - /* Call atexit with the cleanup function. */ mark_used (cleanup); cleanup = build_address (cleanup); - if (flag_use_cxa_atexit && !targetm.cxx.use_atexit_for_cxa_atexit ()) - { - tree addr; - if (use_dtor) - { - /* We must convert CLEANUP to the type that "__cxa_atexit" - expects. */ - cleanup = build_nop (get_atexit_fn_ptr_type (), cleanup); - /* "__cxa_atexit" will pass the address of DECL to the - cleanup function. */ - mark_used (decl); - addr = build_address (decl); - /* The declared type of the parameter to "__cxa_atexit" is - "void *". For plain "T*", we could just let the - machinery in cp_build_function_call convert it -- but if the - type is "cv-qualified T *", then we need to convert it - before passing it in, to avoid spurious errors. */ - addr = build_nop (ptr_type_node, addr); - } - else - /* Since the cleanup functions we build ignore the address - they're given, there's no reason to pass the actual address - in, and, in general, it's cheaper to pass NULL than any - other value. */ - addr = null_pointer_node; - arg2 = cp_build_addr_expr (get_dso_handle_node (), - tf_warning_or_error); - if (targetm.cxx.use_aeabi_atexit ()) + if (DECL_THREAD_LOCAL_P (decl)) + atex_node = get_thread_atexit_node (); + else + atex_node = get_atexit_node (); + + if (use_dtor) + { + /* We must convert CLEANUP to the type that "__cxa_atexit" + expects. */ + cleanup = build_nop (get_atexit_fn_ptr_type (), cleanup); + /* "__cxa_atexit" will pass the address of DECL to the + cleanup function. */ + mark_used (decl); + addr = build_address (decl); + /* The declared type of the parameter to "__cxa_atexit" is + "void *". For plain "T*", we could just let the + machinery in cp_build_function_call convert it -- but if the + type is "cv-qualified T *", then we need to convert it + before passing it in, to avoid spurious errors. */ + addr = build_nop (ptr_type_node, addr); + } + else if (ob_parm) + /* Since the cleanup functions we build ignore the address + they're given, there's no reason to pass the actual address + in, and, in general, it's cheaper to pass NULL than any + other value. */ + addr = null_pointer_node; + + if (dso_parm) + arg2 = cp_build_addr_expr (get_dso_handle_node (), + tf_warning_or_error); + else + arg2 = NULL_TREE; + + if (ob_parm) + { + if (!DECL_THREAD_LOCAL_P (decl) + && targetm.cxx.use_aeabi_atexit ()) { arg1 = cleanup; arg0 = addr; @@ -6769,8 +6795,11 @@ register_dtor_fn (tree decl) } } else - arg0 = cleanup; - return cp_build_function_call_nary (get_atexit_node (), tf_warning_or_error, + { + arg0 = cleanup; + arg1 = NULL_TREE; + } + return cp_build_function_call_nary (atex_node, tf_warning_or_error, arg0, arg1, arg2, NULL_TREE); } diff --git a/gcc/testsuite/ChangeLog b/gcc/testsuite/ChangeLog index 76d0762e829..d575489d2d5 100644 --- a/gcc/testsuite/ChangeLog +++ b/gcc/testsuite/ChangeLog @@ -1,5 +1,10 @@ 2012-10-08 Jason Merrill + * g++.dg/tls/thread_local3.C: New. + * g++.dg/tls/thread_local4.C: New. + * g++.dg/tls/thread_local5.C: New. + * g++.dg/tls/thread_local6.C: New. + * g++.dg/tls/init-2.C: Tweak errors. * g++.dg/tls/thread_local1.C: New. * g++.dg/tls/thread_local2.C: New. diff --git a/gcc/testsuite/g++.dg/tls/thread_local3.C b/gcc/testsuite/g++.dg/tls/thread_local3.C new file mode 100644 index 00000000000..461f1267699 --- /dev/null +++ b/gcc/testsuite/g++.dg/tls/thread_local3.C @@ -0,0 +1,37 @@ +// { dg-do run } +// { dg-require-effective-target c++11 } +// { dg-require-effective-target tls_runtime } +// { dg-require-effective-target pthread } +// { dg-options -pthread } + +int c; +int d; +struct A +{ + A() { ++c; } + ~A() { ++d; } +}; + +void f() +{ + thread_local A a; +} + +void *thread_main(void *) +{ + f(); f(); f(); +} + +#include + +int main() +{ + pthread_t thread; + pthread_create (&thread, 0, thread_main, 0); + pthread_join(thread, 0); + pthread_create (&thread, 0, thread_main, 0); + pthread_join(thread, 0); + + if (c != 2 || d != 2) + __builtin_abort(); +} diff --git a/gcc/testsuite/g++.dg/tls/thread_local4.C b/gcc/testsuite/g++.dg/tls/thread_local4.C new file mode 100644 index 00000000000..53b1f0519be --- /dev/null +++ b/gcc/testsuite/g++.dg/tls/thread_local4.C @@ -0,0 +1,47 @@ +// Test for cleanups with pthread_cancel. + +// { dg-do run } +// { dg-require-effective-target c++11 } +// { dg-require-effective-target tls_runtime } +// { dg-require-effective-target pthread } +// { dg-options -pthread } + +#include +#include + +int c; +int d; +struct A +{ + A() { ++c; } + ~A() { ++d; } +}; + +void f() +{ + thread_local A a; +} + +void *thread_main(void *) +{ + f(); f(); f(); + while (true) + { + pthread_testcancel(); + sleep (1); + } +} + +int main() +{ + pthread_t thread; + pthread_create (&thread, 0, thread_main, 0); + pthread_cancel(thread); + pthread_join(thread, 0); + pthread_create (&thread, 0, thread_main, 0); + pthread_cancel(thread); + pthread_join(thread, 0); + + if (c != 2 || d != 2) + __builtin_abort(); +} diff --git a/gcc/testsuite/g++.dg/tls/thread_local5.C b/gcc/testsuite/g++.dg/tls/thread_local5.C new file mode 100644 index 00000000000..7ce02f62b97 --- /dev/null +++ b/gcc/testsuite/g++.dg/tls/thread_local5.C @@ -0,0 +1,47 @@ +// Test for cleanups in the main thread, too. + +// { dg-do run } +// { dg-require-effective-target c++11 } +// { dg-require-effective-target tls_runtime } +// { dg-require-effective-target pthread } +// { dg-options -pthread } + +#include +#include + +int c; +int d; +struct A +{ + A() { ++c; } + ~A() { + if (++d == 3) + _exit (0); + } +}; + +void f() +{ + thread_local A a; +} + +void *thread_main(void *) +{ + f(); f(); f(); +} + +int main() +{ + pthread_t thread; + thread_main(0); + pthread_create (&thread, 0, thread_main, 0); + pthread_join(thread, 0); + pthread_create (&thread, 0, thread_main, 0); + pthread_join(thread, 0); + + // The dtor for a in the main thread is run after main exits, so we + // return 1 now and override the return value with _exit above. + if (c != 3 || d != 2) + __builtin_abort(); + return 1; +} diff --git a/gcc/testsuite/g++.dg/tls/thread_local6.C b/gcc/testsuite/g++.dg/tls/thread_local6.C new file mode 100644 index 00000000000..118969ae1b3 --- /dev/null +++ b/gcc/testsuite/g++.dg/tls/thread_local6.C @@ -0,0 +1,33 @@ +// Test for cleanups in the main thread without -pthread. + +// { dg-do run } +// { dg-options "-std=c++11" } +// { dg-require-effective-target tls_runtime } + +extern "C" void _exit (int); + +int c; +struct A +{ + A() { ++c; } + ~A() { if (c == 1) _exit(0); } +}; + +void f() +{ + thread_local A a; +} + +void *thread_main(void *) +{ + f(); f(); f(); +} + +int main() +{ + thread_main(0); + + // The dtor for a in the main thread is run after main exits, so we + // return 1 now and override the return value with _exit above. + return 1; +} diff --git a/libstdc++-v3/ChangeLog b/libstdc++-v3/ChangeLog index 998e42924fe..fa7381a35ae 100644 --- a/libstdc++-v3/ChangeLog +++ b/libstdc++-v3/ChangeLog @@ -1,3 +1,10 @@ +2012-10-08 Jason Merrill + + * libsupc++/cxxabi.h: Declare __cxa_thread_atexit. + * libsupc++/atexit_thread.cc: New. + * libsupc++/Makefile.am (nested_exception.lo): Add it. + * config/abi/pre/gnu.ver: Add __cxa_thread_atexit. + 2012-10-07 Matthias Klose * testsuite/28_regex/algorithms/match/basic: Remove empty directory. diff --git a/libstdc++-v3/config/abi/pre/gnu.ver b/libstdc++-v3/config/abi/pre/gnu.ver index 396feec10a2..e23fdfb63b0 100644 --- a/libstdc++-v3/config/abi/pre/gnu.ver +++ b/libstdc++-v3/config/abi/pre/gnu.ver @@ -1531,6 +1531,10 @@ CXXABI_1.3.6 { } CXXABI_1.3.5; +CXXABI_1.3.7 { + __cxa_thread_atexit; +} CXXABI_1.3.6; + # Symbols in the support library (libsupc++) supporting transactional memory. CXXABI_TM_1 { diff --git a/libstdc++-v3/libsupc++/Makefile.am b/libstdc++-v3/libsupc++/Makefile.am index 69cbf5c5b6c..a019bd89432 100644 --- a/libstdc++-v3/libsupc++/Makefile.am +++ b/libstdc++-v3/libsupc++/Makefile.am @@ -48,6 +48,7 @@ endif sources = \ array_type_info.cc \ atexit_arm.cc \ + atexit_thread.cc \ bad_alloc.cc \ bad_cast.cc \ bad_typeid.cc \ @@ -123,6 +124,11 @@ guard.lo: guard.cc guard.o: guard.cc $(CXXCOMPILE) -std=gnu++0x -c $< +atexit_thread.lo: atexit_thread.cc + $(LTCXXCOMPILE) -std=gnu++0x -c $< +atexit_thread.o: atexit_thread.cc + $(CXXCOMPILE) -std=gnu++0x -c $< + nested_exception.lo: nested_exception.cc $(LTCXXCOMPILE) -std=gnu++0x -c $< nested_exception.o: nested_exception.cc diff --git a/libstdc++-v3/libsupc++/Makefile.in b/libstdc++-v3/libsupc++/Makefile.in index b2af9ba5fb4..e745179cd31 100644 --- a/libstdc++-v3/libsupc++/Makefile.in +++ b/libstdc++-v3/libsupc++/Makefile.in @@ -90,7 +90,7 @@ am__installdirs = "$(DESTDIR)$(toolexeclibdir)" "$(DESTDIR)$(bitsdir)" \ "$(DESTDIR)$(stddir)" LTLIBRARIES = $(noinst_LTLIBRARIES) $(toolexeclib_LTLIBRARIES) libsupc___la_LIBADD = -am__objects_1 = array_type_info.lo atexit_arm.lo bad_alloc.lo \ +am__objects_1 = array_type_info.lo atexit_arm.lo atexit_thread.lo bad_alloc.lo \ bad_cast.lo bad_typeid.lo class_type_info.lo del_op.lo \ del_opnt.lo del_opv.lo del_opvnt.lo dyncast.lo eh_alloc.lo \ eh_arm.lo eh_aux_runtime.lo eh_call.lo eh_catch.lo \ @@ -362,6 +362,7 @@ headers = $(std_HEADERS) $(bits_HEADERS) sources = \ array_type_info.cc \ atexit_arm.cc \ + atexit_thread.cc \ bad_alloc.cc \ bad_cast.cc \ bad_typeid.cc \ @@ -800,6 +801,11 @@ guard.lo: guard.cc guard.o: guard.cc $(CXXCOMPILE) -std=gnu++0x -c $< +atexit_thread.lo: atexit_thread.cc + $(LTCXXCOMPILE) -std=gnu++0x -c $< +atexit_thread.o: atexit_thread.cc + $(CXXCOMPILE) -std=gnu++0x -c $< + nested_exception.lo: nested_exception.cc $(LTCXXCOMPILE) -std=gnu++0x -c $< nested_exception.o: nested_exception.cc diff --git a/libstdc++-v3/libsupc++/atexit_thread.cc b/libstdc++-v3/libsupc++/atexit_thread.cc new file mode 100644 index 00000000000..5e47708d934 --- /dev/null +++ b/libstdc++-v3/libsupc++/atexit_thread.cc @@ -0,0 +1,135 @@ +// Copyright (C) 2012 Free Software Foundation, Inc. +// +// This file is part of GCC. +// +// GCC is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 3, or (at your option) +// any later version. + +// GCC is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// Under Section 7 of GPL version 3, you are granted additional +// permissions described in the GCC Runtime Library Exception, version +// 3.1, as published by the Free Software Foundation. + +// You should have received a copy of the GNU General Public License and +// a copy of the GCC Runtime Library Exception along with this program; +// see the files COPYING3 and COPYING.RUNTIME respectively. If not, see +// . + +#include +#include +#include +#include "bits/gthr.h" + +namespace { + // Data structure for the list of destructors: Singly-linked list + // of arrays. + class list + { + struct elt + { + void *object; + void (*destructor)(void *); + }; + + static const int max_nelts = 32; + + list *next; + int nelts; + elt array[max_nelts]; + + elt *allocate_elt(); + public: + void run(); + static void run(void *p); + int add_elt(void (*)(void *), void *); + }; + + // Return the address of an open slot. + list::elt * + list::allocate_elt() + { + if (nelts < max_nelts) + return &array[nelts++]; + if (!next) + next = new (std::nothrow) list(); + if (!next) + return 0; + return next->allocate_elt(); + } + + // Run all the cleanups in the list. + void + list::run() + { + for (int i = nelts - 1; i >= 0; --i) + array[i].destructor (array[i].object); + if (next) + next->run(); + } + + // Static version to use as a callback to __gthread_key_create. + void + list::run(void *p) + { + static_cast(p)->run(); + } + + // The list of cleanups is per-thread. + thread_local list first; + + // The pthread data structures for actually running the destructors at + // thread exit are shared. The constructor of the thread-local sentinel + // object in add_elt performs the initialization. + __gthread_key_t key; + __gthread_once_t once = __GTHREAD_ONCE_INIT; + void run_current () { first.run(); } + void key_init() { + __gthread_key_create (&key, list::run); + // Also make sure the destructors are run by std::exit. + // FIXME TLS cleanups should run before static cleanups and atexit + // cleanups. + std::atexit (run_current); + } + struct sentinel + { + sentinel() + { + if (__gthread_active_p ()) + { + __gthread_once (&once, key_init); + __gthread_setspecific (key, &first); + } + else + std::atexit (run_current); + } + }; + + // Actually insert an element. + int + list::add_elt(void (*dtor)(void *), void *obj) + { + thread_local sentinel s; + elt *e = allocate_elt (); + if (!e) + return -1; + e->object = obj; + e->destructor = dtor; + return 0; + } +} + +namespace __cxxabiv1 +{ + extern "C" int + __cxa_thread_atexit (void (*dtor)(void *), void *obj, void */*dso_handle*/) + _GLIBCXX_NOTHROW + { + return first.add_elt (dtor, obj); + } +} diff --git a/libstdc++-v3/libsupc++/cxxabi.h b/libstdc++-v3/libsupc++/cxxabi.h index b924fc17079..582c435435d 100644 --- a/libstdc++-v3/libsupc++/cxxabi.h +++ b/libstdc++-v3/libsupc++/cxxabi.h @@ -134,6 +134,10 @@ namespace __cxxabiv1 int __cxa_finalize(void*); + // TLS destruction. + int + __cxa_thread_atexit(void (*)(void*), void*, void *) _GLIBCXX_NOTHROW; + // Pure virtual functions. void __cxa_pure_virtual(void) __attribute__ ((__noreturn__));