]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
Support C++11 thread_local destructors.
authorJason Merrill <jason@redhat.com>
Mon, 8 Oct 2012 14:45:24 +0000 (10:45 -0400)
committerJason Merrill <jason@gcc.gnu.org>
Mon, 8 Oct 2012 14:45:24 +0000 (10:45 -0400)
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

13 files changed:
gcc/cp/ChangeLog
gcc/cp/decl.c
gcc/testsuite/ChangeLog
gcc/testsuite/g++.dg/tls/thread_local3.C [new file with mode: 0644]
gcc/testsuite/g++.dg/tls/thread_local4.C [new file with mode: 0644]
gcc/testsuite/g++.dg/tls/thread_local5.C [new file with mode: 0644]
gcc/testsuite/g++.dg/tls/thread_local6.C [new file with mode: 0644]
libstdc++-v3/ChangeLog
libstdc++-v3/config/abi/pre/gnu.ver
libstdc++-v3/libsupc++/Makefile.am
libstdc++-v3/libsupc++/Makefile.in
libstdc++-v3/libsupc++/atexit_thread.cc [new file with mode: 0644]
libstdc++-v3/libsupc++/cxxabi.h

index 871dbaa85300d2d173f1ba04edd636ff5d0187fa..1807f5b0e435ecb5fdcb0c999e7163a90c6ee795 100644 (file)
@@ -1,5 +1,8 @@
 2012-10-08  Jason Merrill  <jason@redhat.com>
 
+       * 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.
index b409c34eee1003d19e31799d6a242636305a73ea..7dc13fb94a35bc99182af05ca13cbfb160a86d5e 100644 (file)
@@ -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 ())
+
+  if (DECL_THREAD_LOCAL_P (decl))
+    atex_node = get_thread_atexit_node ();
+  else
+    atex_node = get_atexit_node ();
+
+  if (use_dtor)
     {
-      tree addr;
+      /* 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 (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 (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);
 }
 
index 76d0762e829ae11fd4979ba33deacd87589faa50..d575489d2d5fe5de3c1bf1f3a2e3ed4318fa30cd 100644 (file)
@@ -1,5 +1,10 @@
 2012-10-08  Jason Merrill  <jason@redhat.com>
 
+       * 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 (file)
index 0000000..461f126
--- /dev/null
@@ -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 <pthread.h>
+
+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 (file)
index 0000000..53b1f05
--- /dev/null
@@ -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 <pthread.h>
+#include <unistd.h>
+
+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 (file)
index 0000000..7ce02f6
--- /dev/null
@@ -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 <pthread.h>
+#include <unistd.h>
+
+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 (file)
index 0000000..118969a
--- /dev/null
@@ -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;
+}
index 998e42924fe39b92eab5915aef65cbfe4fd2ccf5..fa7381a35ae89ff89df52eed65c12c28b01face8 100644 (file)
@@ -1,3 +1,10 @@
+2012-10-08  Jason Merrill  <jason@redhat.com>
+
+       * 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  <doko@ubuntu.com>
 
        * testsuite/28_regex/algorithms/match/basic: Remove empty directory.
index 396feec10a2470b6775e194a98788e52b297b208..e23fdfb63b06b9ee65369ee2a14bfd47e17ba79a 100644 (file)
@@ -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 {
index 69cbf5c5b6cd8f4d62ece518e300ed7d915d4a97..a019bd894329882131e0a9808e122a5820aa72ca 100644 (file)
@@ -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
index b2af9ba5fb407e65e298e3351fd31e6fdc8c4790..e745179cd3187bef4a4392487e767e4c2de29ef9 100644 (file)
@@ -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 (file)
index 0000000..5e47708
--- /dev/null
@@ -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
+// <http://www.gnu.org/licenses/>.
+
+#include <cxxabi.h>
+#include <cstdlib>
+#include <new>
+#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<list *>(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);
+  }
+}
index b924fc1707959ddd3eeb02fe1da5bd9f7a595a7d..582c435435d2eb0db43de8eae0b04d1fe7717ef3 100644 (file)
@@ -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__));