mirror of git://gcc.gnu.org/git/gcc.git
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
This commit is contained in:
parent
b1db7f9139
commit
5b031b9b56
|
|
@ -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.
|
||||
|
|
|
|||
119
gcc/cp/decl.c
119
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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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__));
|
||||
|
|
|
|||
Loading…
Reference in New Issue