Add test case for C++11 thread_local support
diff mbox

Message ID 56129C50.3020008@redhat.com
State New
Headers show

Commit Message

Florian Weimer Oct. 5, 2015, 3:50 p.m. UTC
We did not have a test case which tests the thread_local support
functionality with the C++ compiler.

I tested this on Fedora 22 (x86_64), Red Hat Enterprise Linux 7 (ppc64),
and Debian wheezy (x86_64, after commenting out AVX512 support in the
dynamic linker).  The latter correctly marks the test case as UNSUPPORTED.

Florian

Comments

Andreas Schwab Oct. 5, 2015, 5:52 p.m. UTC | #1
Florian Weimer <fweimer@redhat.com> writes:

> diff --git a/nptl/tst-thread_local1.cc b/nptl/tst-thread_local1.cc
> new file mode 100644
> index 0000000..65d374b
> --- /dev/null
> +++ b/nptl/tst-thread_local1.cc
> @@ -0,0 +1,200 @@
> +/* Test basic thread_local support.
> +   Copyright (C) 2015 Free Software Foundation, Inc.
> +   This file is part of the GNU C Library.
> +   Contributed by Ulrich Drepper <drepper@redhat.com>, 2002.

This is probably not true, and we don't add contributer lines any more
anyway.

Andreas.
Carlos O'Donell Oct. 5, 2015, 6:19 p.m. UTC | #2
On 10/05/2015 11:50 AM, Florian Weimer wrote:
> We did not have a test case which tests the thread_local support
> functionality with the C++ compiler.
>
> I tested this on Fedora 22 (x86_64), Red Hat Enterprise Linux 7 (ppc64),
> and Debian wheezy (x86_64, after commenting out AVX512 support in the
> dynamic linker).  The latter correctly marks the test case as UNSUPPORTED.

* Why isn't the C++ compiler testsuite sufficient?

  Is there any reason to have this test in glibc? In a normal bootstrap you'd
  build glibc, you'd test thread_local destructor support via tst-tls-atexit
  (the part implemented by glibc), and then you'd go on to build the full
  set of languages in the compiler and test C++ thread_local support more fully.
  Why do we want to test this here?  

* Testing glibc thread_local destructor support?

  If glibc thread_local support is broken, then libstdc++-v3/configure won't
  detect __cxa_thread_atexit_impl and libstdc++-v3/libsupc++/atexit_thread.cc
  is built with alternate compiler-specific support for destructors. Similarly
  if the compiler you're using was built with old-enough glibc to lack that support.

  Therefore a pre-requisite of this test is that it must know that the existing
  C++ compiler is built to use glibc's __cxa_thread_atexit_impl, otherwise you're
  testing libsupc++'s support for thread_local destructors and the test has nothing
  to do with glibc? 

* Had a quibble with a comment in nptl/Makefile, see below.

Some top-level process nits:

* Don't include ChangeLog in diff.

  Put it into your email at the end please. I promise we'll get to the point
  where it's autogenerated and you won't have to care about it (same with NEWS).

* Don't include regenerated file changes in diff.

  They make it harder to read the patch quickly, and are not needed.
  The committer has to regenerate the file and review what they checkin.

> 0001-Add-a-test-case-for-C-11-thread_local-support.patch
> 
> 
> From b2d51b5b9152d841f3476d7d5588ce302dad1853 Mon Sep 17 00:00:00 2001
> Message-Id: <b2d51b5b9152d841f3476d7d5588ce302dad1853.1444060169.git.fweimer@redhat.com>
> From: Florian Weimer <fweimer@redhat.com>
> Date: Mon, 5 Oct 2015 17:48:12 +0200
> Subject: [PATCH] Add a test case for C++11 thread_local support
> To: libc-alpha@sourceware.org
> 
> This requires a C++ compiler with thread_local support, and a new
> configure check is needed.

OK.

> ---
>  ChangeLog                 |  12 +++
>  config.make.in            |   1 +
>  configure                 |  47 +++++++++++
>  configure.ac              |  25 ++++++
>  nptl/Makefile             |   9 ++-
>  nptl/tst-thread_local1.cc | 200 ++++++++++++++++++++++++++++++++++++++++++++++
>  6 files changed, 293 insertions(+), 1 deletion(-)
>  create mode 100644 nptl/tst-thread_local1.cc
> 
> diff --git a/ChangeLog b/ChangeLog
> index 0bfe2bb..804a2a1 100644
> --- a/ChangeLog
> +++ b/ChangeLog
> @@ -1,3 +1,15 @@
> +2015-10-05  Florian Weimer  <fweimer@redhat.com>
> +
> +	* configure.ac (libc_cv_cxx_thread_local): Define.
> +	* configure: Regenerate.
> +	* config.make.in (have-cxx-thread_local): Define.
> +	* nptl/Makefile (CFLAGS-tst-thread_local1.o):
> +	(LDLIBS-tst-thread_local1): Define.
> +	(tests): Add tst-thread_local1.
> +	[have-cxx-thread_local != yes] (tests-unsupported): Add
> +	tst-thread_local1.
> +	* nptl/tst-thread_local1.cc: New file.
> +
>  2015-10-03  Paul Pluzhnikov  <ppluzhnikov@google.com>
>  
>  	* sysdeps/x86_64/fpu/libm-test-ulps: Regenerated.
> diff --git a/config.make.in b/config.make.in
> index bea371d..839d86f 100644
> --- a/config.make.in
> +++ b/config.make.in
> @@ -68,6 +68,7 @@ bind-now = @bindnow@
>  have-hash-style = @libc_cv_hashstyle@
>  use-default-link = @use_default_link@
>  output-format = @libc_cv_output_format@
> +have-cxx-thread_local = @libc_cv_cxx_thread_local@

OK.
  
>  static-libgcc = @libc_cv_gcc_static_libgcc@
>  

[cut diff of configure]

> diff --git a/configure.ac b/configure.ac
> index 95d700e..68e6777 100644
> --- a/configure.ac
> +++ b/configure.ac
> @@ -1982,6 +1982,31 @@ if test $libc_cv_builtin_trap = yes; then
>    AC_DEFINE([HAVE_BUILTIN_TRAP])
>  fi
>  
> +dnl C++ feature tests.
> +AC_LANG_PUSH([C++])
> +
> +AC_CACHE_CHECK([whether the C++ compiler supports thread_local],
> +	       libc_cv_cxx_thread_local, [
> +old_CXXFLAGS="$CXXFLAGS"
> +CXXFLAGS="$CXXFLAGS -std=gnu++11"
> +AC_COMPILE_IFELSE([AC_LANG_SOURCE([
> +struct S
> +{
> +  S ();
> +  ~S ();
> +};
> +thread_local S s;
> +S * get () { return &s; }
> +])],
> +	       [libc_cv_cxx_thread_local=yes],
> +	       [libc_cv_cxx_thread_local=no])
> +CXXFLAGS="$old_CXXFLAGS"
> +])
> +AC_SUBST(libc_cv_cxx_thread_local)
> +
> +AC_LANG_POP([C++])
> +dnl End of C++ feature tests.

OK.

> +
>  ### End of automated tests.
>  ### Now run sysdeps configure fragments.
>  
> diff --git a/nptl/Makefile b/nptl/Makefile
> index aaca0a4..b27bef1 100644
> --- a/nptl/Makefile
> +++ b/nptl/Makefile
> @@ -212,6 +212,8 @@ CFLAGS-recvfrom.c = -fexceptions -fasynchronous-unwind-tables
>  CFLAGS-pt-system.c = -fexceptions
>  
>  LDLIBS-tst-once5 = -lstdc++
> +CFLAGS-tst-thread_local1.o = -std=gnu++11
> +LDLIBS-tst-thread_local1 = -lstdc++

OK.

>  
>  tests = tst-typesizes \
>  	tst-attr1 tst-attr2 tst-attr3 tst-default-attr \
> @@ -283,7 +285,8 @@ tests = tst-typesizes \
>  	tst-getpid3 \
>  	tst-setuid3 \
>  	tst-initializers1 $(addprefix tst-initializers1-,c89 gnu89 c99 gnu99) \
> -	tst-bad-schedattr
> +	tst-bad-schedattr \
> +	tst-thread_local1

OK.

>  xtests = tst-setuid1 tst-setuid1-static tst-setuid2 \
>  	tst-mutexpp1 tst-mutexpp6 tst-mutexpp10
>  test-srcs = tst-oddstacklimit
> @@ -403,6 +406,10 @@ ifeq (,$(CXX))
>  # These tests require a C++ compiler and runtime.
>  tests-unsupported += tst-cancel24 tst-cancel24-static tst-once5
>  endif
> +# These tests require a C++ compiler with thread_local support.

This should say:
# These tests require a C++ compiler and runtime with thread_local support.

> +ifneq ($(have-cxx-thread_local),yes)
> +tests-unsupported += tst-thread_local1
> +endif
>  
>  include ../Rules
>  
> diff --git a/nptl/tst-thread_local1.cc b/nptl/tst-thread_local1.cc
> new file mode 100644
> index 0000000..65d374b
> --- /dev/null
> +++ b/nptl/tst-thread_local1.cc
> @@ -0,0 +1,200 @@
> +/* Test basic thread_local support.

It's a fine test, but I'm not sure why this needs to be in glibc and not
the libstdc++ testsuite. I count 31 thread_local related tests in gcc,
with 27 being specifically about thread_local.

> +   Copyright (C) 2015 Free Software Foundation, Inc.
> +   This file is part of the GNU C Library.
> +   Contributed by Ulrich Drepper <drepper@redhat.com>, 2002.
> +
> +   The GNU C Library is free software; you can redistribute it and/or
> +   modify it under the terms of the GNU Lesser General Public
> +   License as published by the Free Software Foundation; either
> +   version 2.1 of the License, or (at your option) any later version.
> +
> +   The GNU C Library 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
> +   Lesser General Public License for more details.
> +
> +   You should have received a copy of the GNU Lesser General Public
> +   License along with the GNU C Library; if not, see
> +   <http://www.gnu.org/licenses/>.  */
> +
> +#include <errno.h>
> +#include <pthread.h>
> +#include <stdio.h>
> +#include <string.h>
> +
> +#include <functional>
> +#include <string>
> +#include <thread>
> +
> +struct counter
> +{
> +  int constructed {};
> +  int destructed {};
> +
> +  void reset ();
> +};
> +
> +void
> +counter::reset ()
> +{
> +  constructed = 0;
> +  destructed = 0;
> +}
> +
> +static std::string
> +to_string (const counter &c)
> +{
> +  char buf[128];
> +  snprintf (buf, sizeof (buf), "%d/%d",
> +            c.constructed, c.destructed);
> +  return buf;
> +}
> +
> +template <counter *Counter>
> +struct counting
> +{
> +  counting () __attribute__ ((noinline, noclone));
> +  ~counting () __attribute__ ((noinline, noclone));
> +  void operation () __attribute__ ((noinline, noclone));
> +};
> +
> +template<counter *Counter>
> +__attribute__ ((noinline, noclone))
> +counting<Counter>::counting ()
> +{
> +  ++Counter->constructed;
> +}
> +
> +template<counter *Counter>
> +__attribute__ ((noinline, noclone))
> +counting<Counter>::~counting ()
> +{
> +  ++Counter->destructed;
> +}
> +
> +template<counter *Counter>
> +void __attribute__ ((noinline, noclone))
> +counting<Counter>::operation ()
> +{
> +  // Optimization barrier.
> +  asm ("");
> +}
> +
> +static counter counter_static;
> +static counter counter_anonymous_namespace;
> +static counter counter_extern;
> +static counter counter_function_local;
> +static bool errors (false);
> +
> +static std::string
> +all_counters ()
> +{
> +  return to_string (counter_static)
> +    + ' ' + to_string (counter_anonymous_namespace)
> +    + ' ' + to_string (counter_extern)
> +    + ' ' + to_string (counter_function_local);
> +}
> +
> +static void
> +check_counters (const char *name, const char *expected)
> +{
> +  std::string actual{all_counters ()};
> +  if (actual != expected)
> +    {
> +      printf ("error: %s: (%s) != (%s)\n",
> +              name, actual.c_str (), expected);
> +      errors = true;
> +    }
> +}
> +
> +static void
> +reset_all ()
> +{
> +  counter_static.reset ();
> +  counter_anonymous_namespace.reset ();
> +  counter_extern.reset ();
> +  counter_function_local.reset ();
> +}
> +
> +static thread_local counting<&counter_static> counting_static;
> +namespace {
> +  thread_local counting<&counter_anonymous_namespace>
> +    counting_anonymous_namespace;
> +}
> +extern thread_local counting<&counter_extern> counting_extern;
> +thread_local counting<&counter_extern> counting_extern;
> +
> +static void *
> +thread_without_access (void *)
> +{
> +  return nullptr;
> +}
> +
> +static void *
> +thread_with_access (void *)
> +{
> +  thread_local counting<&counter_function_local> counting_function_local;
> +  counting_function_local.operation ();
> +  check_counters ("early in thread_with_access", "0/0 0/0 0/0 1/0");
> +  counting_static.operation ();
> +  counting_anonymous_namespace.operation ();
> +  counting_extern.operation ();
> +  check_counters ("in thread_with_access", "1/0 1/0 1/0 1/0");
> +  return nullptr;
> +}
> +
> +static int
> +do_test (void)
> +{
> +  std::function<void (void *(void *))> do_pthread =
> +    [](void *(func) (void *))
> +    {
> +      pthread_t thr;
> +      int ret = pthread_create (&thr, nullptr, func, nullptr);
> +      if (ret != 0)
> +        {
> +          errno = ret;
> +          printf ("error: pthread_create: %m\n");
> +          errors = true;
> +          return;
> +        }
> +      ret = pthread_join (thr, nullptr);
> +      if (ret != 0)
> +        {
> +          errno = ret;
> +          printf ("error: pthread_join: %m\n");
> +          errors = true;
> +          return;
> +        }
> +    };
> +  std::function<void (void *(void *))> do_std_thread =
> +    [](void *(func) (void *))
> +    {
> +      std::thread thr{[func] {func (nullptr);}};
> +      thr.join ();
> +    };
> +
> +  std::array<std::pair<const char *, std::function<void (void *(void *))>>, 2>
> +    do_thread_X
> +      {{
> +        {"pthread_create", do_pthread},
> +        {"std::thread", do_std_thread},
> +      }};
> +
> +  for (auto do_thread : do_thread_X)
> +    {
> +      printf ("info: testing %s\n", do_thread.first);
> +      check_counters ("initial", "0/0 0/0 0/0 0/0");
> +      do_thread.second (thread_without_access);
> +      check_counters ("after thread_without_access", "0/0 0/0 0/0 0/0");
> +      reset_all ();
> +      do_thread.second (thread_with_access);
> +      check_counters ("after thread_with_access", "1/1 1/1 1/1 1/1");
> +      reset_all ();
> +    }
> +
> +  return errors;
> +}
> +
> +#define TEST_FUNCTION do_test ()
> +#include "../test-skeleton.c"
> -- 2.4.3

Cheers,
Carlos.
Florian Weimer Oct. 5, 2015, 6:37 p.m. UTC | #3
On 10/05/2015 08:19 PM, Carlos O'Donell wrote:
> On 10/05/2015 11:50 AM, Florian Weimer wrote:
>> We did not have a test case which tests the thread_local support
>> functionality with the C++ compiler.
>>
>> I tested this on Fedora 22 (x86_64), Red Hat Enterprise Linux 7 (ppc64),
>> and Debian wheezy (x86_64, after commenting out AVX512 support in the
>> dynamic linker).  The latter correctly marks the test case as UNSUPPORTED.
> 
> * Why isn't the C++ compiler testsuite sufficient?
> 
>   Is there any reason to have this test in glibc? In a normal bootstrap you'd
>   build glibc, you'd test thread_local destructor support via tst-tls-atexit
>   (the part implemented by glibc), and then you'd go on to build the full
>   set of languages in the compiler and test C++ thread_local support more fully.
>   Why do we want to test this here?  

I want to prevent regressions due to glibc changes.

This test case covers only the basics, we also need to check interaction
with other termination callback facilities in glibc (atexit, POSIX
thread-local storage), and the interaction with cancellation and
dlclose.  We also need white-box testing for memory allocation failures.
 I think the dependency on thread_local inside glibc is harmless against
all the baggage these tests would pull into libstdc++.

This test is just a start.  Basically, this test is the minimal version
I was sufficiently confident would actually pass testing.

> * Testing glibc thread_local destructor support?
> 
>   If glibc thread_local support is broken, then libstdc++-v3/configure won't
>   detect __cxa_thread_atexit_impl and libstdc++-v3/libsupc++/atexit_thread.cc
>   is built with alternate compiler-specific support for destructors. Similarly
>   if the compiler you're using was built with old-enough glibc to lack that support.
> 
>   Therefore a pre-requisite of this test is that it must know that the existing
>   C++ compiler is built to use glibc's __cxa_thread_atexit_impl, otherwise you're
>   testing libsupc++'s support for thread_local destructors and the test has nothing
>   to do with glibc?

Hmm, you are right.  It is actually a libstdc++ matter.  I guess I can
work around that by interposing __cxxabiv1::__cxa_thread_atexit.  So
maybe we should run this test twice, once as it is now, and once with
the interposed symbol.

Or we can change the fallback implementation of
__cxxabiv1::__cxa_thread_atexit in libstdc++ to check a weak symbol
__cxxabiv1::__cxa_thread_atexit_impl first and use that if it is
available.  Maybe this is useful on its own?

> * Had a quibble with a comment in nptl/Makefile, see below.
> 
> Some top-level process nits:
> 
> * Don't include ChangeLog in diff.
> 
>   Put it into your email at the end please. I promise we'll get to the point
>   where it's autogenerated and you won't have to care about it (same with NEWS).

I've been told before that either way is fine. :-/

I can certainly script something to extract the changelog and place it
outside the diff.

>>  xtests = tst-setuid1 tst-setuid1-static tst-setuid2 \
>>  	tst-mutexpp1 tst-mutexpp6 tst-mutexpp10
>>  test-srcs = tst-oddstacklimit
>> @@ -403,6 +406,10 @@ ifeq (,$(CXX))
>>  # These tests require a C++ compiler and runtime.
>>  tests-unsupported += tst-cancel24 tst-cancel24-static tst-once5
>>  endif
>> +# These tests require a C++ compiler with thread_local support.
> 
> This should say:
> # These tests require a C++ compiler and runtime with thread_local support.

Fixed.

> It's a fine test, but I'm not sure why this needs to be in glibc and not
> the libstdc++ testsuite. I count 31 thread_local related tests in gcc,
> with 27 being specifically about thread_local.

See above.

Florian
Florian Weimer Oct. 5, 2015, 6:39 p.m. UTC | #4
On 10/05/2015 07:52 PM, Andreas Schwab wrote:
> Florian Weimer <fweimer@redhat.com> writes:
> 
>> diff --git a/nptl/tst-thread_local1.cc b/nptl/tst-thread_local1.cc
>> new file mode 100644
>> index 0000000..65d374b
>> --- /dev/null
>> +++ b/nptl/tst-thread_local1.cc
>> @@ -0,0 +1,200 @@
>> +/* Test basic thread_local support.
>> +   Copyright (C) 2015 Free Software Foundation, Inc.
>> +   This file is part of the GNU C Library.
>> +   Contributed by Ulrich Drepper <drepper@redhat.com>, 2002.
> 
> This is probably not true, and we don't add contributer lines any more
> anyway.

Fixed, thanks.  I did copy this from a Copyright (C) 2015 file.  Looks
like I picked the only one with this problem.

Florian

Patch
diff mbox

From b2d51b5b9152d841f3476d7d5588ce302dad1853 Mon Sep 17 00:00:00 2001
Message-Id: <b2d51b5b9152d841f3476d7d5588ce302dad1853.1444060169.git.fweimer@redhat.com>
From: Florian Weimer <fweimer@redhat.com>
Date: Mon, 5 Oct 2015 17:48:12 +0200
Subject: [PATCH] Add a test case for C++11 thread_local support
To: libc-alpha@sourceware.org

This requires a C++ compiler with thread_local support, and a new
configure check is needed.
---
 ChangeLog                 |  12 +++
 config.make.in            |   1 +
 configure                 |  47 +++++++++++
 configure.ac              |  25 ++++++
 nptl/Makefile             |   9 ++-
 nptl/tst-thread_local1.cc | 200 ++++++++++++++++++++++++++++++++++++++++++++++
 6 files changed, 293 insertions(+), 1 deletion(-)
 create mode 100644 nptl/tst-thread_local1.cc

diff --git a/ChangeLog b/ChangeLog
index 0bfe2bb..804a2a1 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,15 @@ 
+2015-10-05  Florian Weimer  <fweimer@redhat.com>
+
+	* configure.ac (libc_cv_cxx_thread_local): Define.
+	* configure: Regenerate.
+	* config.make.in (have-cxx-thread_local): Define.
+	* nptl/Makefile (CFLAGS-tst-thread_local1.o):
+	(LDLIBS-tst-thread_local1): Define.
+	(tests): Add tst-thread_local1.
+	[have-cxx-thread_local != yes] (tests-unsupported): Add
+	tst-thread_local1.
+	* nptl/tst-thread_local1.cc: New file.
+
 2015-10-03  Paul Pluzhnikov  <ppluzhnikov@google.com>
 
 	* sysdeps/x86_64/fpu/libm-test-ulps: Regenerated.
diff --git a/config.make.in b/config.make.in
index bea371d..839d86f 100644
--- a/config.make.in
+++ b/config.make.in
@@ -68,6 +68,7 @@  bind-now = @bindnow@
 have-hash-style = @libc_cv_hashstyle@
 use-default-link = @use_default_link@
 output-format = @libc_cv_output_format@
+have-cxx-thread_local = @libc_cv_cxx_thread_local@
 
 static-libgcc = @libc_cv_gcc_static_libgcc@
 
diff --git a/configure b/configure
index fe402ea..0d19d05 100755
--- a/configure
+++ b/configure
@@ -614,6 +614,7 @@  use_nscd
 libc_cv_gcc_unwind_find_fde
 libc_extra_cppflags
 libc_extra_cflags
+libc_cv_cxx_thread_local
 CPPUNDEFS
 sizeof_long_double
 have_selinux
@@ -7215,6 +7216,52 @@  if test $libc_cv_builtin_trap = yes; then
 
 fi
 
+ac_ext=cpp
+ac_cpp='$CXXCPP $CPPFLAGS'
+ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_cxx_compiler_gnu
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the C++ compiler supports thread_local" >&5
+$as_echo_n "checking whether the C++ compiler supports thread_local... " >&6; }
+if ${libc_cv_cxx_thread_local+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+
+old_CXX="$CXX"
+CXX="$CXX -std=gnu++11"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+struct S {
+  S();
+  ~S();
+};
+thread_local S s;
+S * get() { return &s; }
+
+_ACEOF
+if ac_fn_cxx_try_compile "$LINENO"; then :
+  libc_cv_cxx_thread_local=yes
+else
+  libc_cv_cxx_thread_local=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+CXX="$old_CXX"
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $libc_cv_cxx_thread_local" >&5
+$as_echo "$libc_cv_cxx_thread_local" >&6; }
+
+
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
 ### End of automated tests.
 ### Now run sysdeps configure fragments.
 
diff --git a/configure.ac b/configure.ac
index 95d700e..68e6777 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1982,6 +1982,31 @@  if test $libc_cv_builtin_trap = yes; then
   AC_DEFINE([HAVE_BUILTIN_TRAP])
 fi
 
+dnl C++ feature tests.
+AC_LANG_PUSH([C++])
+
+AC_CACHE_CHECK([whether the C++ compiler supports thread_local],
+	       libc_cv_cxx_thread_local, [
+old_CXXFLAGS="$CXXFLAGS"
+CXXFLAGS="$CXXFLAGS -std=gnu++11"
+AC_COMPILE_IFELSE([AC_LANG_SOURCE([
+struct S
+{
+  S ();
+  ~S ();
+};
+thread_local S s;
+S * get () { return &s; }
+])],
+	       [libc_cv_cxx_thread_local=yes],
+	       [libc_cv_cxx_thread_local=no])
+CXXFLAGS="$old_CXXFLAGS"
+])
+AC_SUBST(libc_cv_cxx_thread_local)
+
+AC_LANG_POP([C++])
+dnl End of C++ feature tests.
+
 ### End of automated tests.
 ### Now run sysdeps configure fragments.
 
diff --git a/nptl/Makefile b/nptl/Makefile
index aaca0a4..b27bef1 100644
--- a/nptl/Makefile
+++ b/nptl/Makefile
@@ -212,6 +212,8 @@  CFLAGS-recvfrom.c = -fexceptions -fasynchronous-unwind-tables
 CFLAGS-pt-system.c = -fexceptions
 
 LDLIBS-tst-once5 = -lstdc++
+CFLAGS-tst-thread_local1.o = -std=gnu++11
+LDLIBS-tst-thread_local1 = -lstdc++
 
 tests = tst-typesizes \
 	tst-attr1 tst-attr2 tst-attr3 tst-default-attr \
@@ -283,7 +285,8 @@  tests = tst-typesizes \
 	tst-getpid3 \
 	tst-setuid3 \
 	tst-initializers1 $(addprefix tst-initializers1-,c89 gnu89 c99 gnu99) \
-	tst-bad-schedattr
+	tst-bad-schedattr \
+	tst-thread_local1
 xtests = tst-setuid1 tst-setuid1-static tst-setuid2 \
 	tst-mutexpp1 tst-mutexpp6 tst-mutexpp10
 test-srcs = tst-oddstacklimit
@@ -403,6 +406,10 @@  ifeq (,$(CXX))
 # These tests require a C++ compiler and runtime.
 tests-unsupported += tst-cancel24 tst-cancel24-static tst-once5
 endif
+# These tests require a C++ compiler with thread_local support.
+ifneq ($(have-cxx-thread_local),yes)
+tests-unsupported += tst-thread_local1
+endif
 
 include ../Rules
 
diff --git a/nptl/tst-thread_local1.cc b/nptl/tst-thread_local1.cc
new file mode 100644
index 0000000..65d374b
--- /dev/null
+++ b/nptl/tst-thread_local1.cc
@@ -0,0 +1,200 @@ 
+/* Test basic thread_local support.
+   Copyright (C) 2015 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+   Contributed by Ulrich Drepper <drepper@redhat.com>, 2002.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library 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
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#include <errno.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <functional>
+#include <string>
+#include <thread>
+
+struct counter
+{
+  int constructed {};
+  int destructed {};
+
+  void reset ();
+};
+
+void
+counter::reset ()
+{
+  constructed = 0;
+  destructed = 0;
+}
+
+static std::string
+to_string (const counter &c)
+{
+  char buf[128];
+  snprintf (buf, sizeof (buf), "%d/%d",
+            c.constructed, c.destructed);
+  return buf;
+}
+
+template <counter *Counter>
+struct counting
+{
+  counting () __attribute__ ((noinline, noclone));
+  ~counting () __attribute__ ((noinline, noclone));
+  void operation () __attribute__ ((noinline, noclone));
+};
+
+template<counter *Counter>
+__attribute__ ((noinline, noclone))
+counting<Counter>::counting ()
+{
+  ++Counter->constructed;
+}
+
+template<counter *Counter>
+__attribute__ ((noinline, noclone))
+counting<Counter>::~counting ()
+{
+  ++Counter->destructed;
+}
+
+template<counter *Counter>
+void __attribute__ ((noinline, noclone))
+counting<Counter>::operation ()
+{
+  // Optimization barrier.
+  asm ("");
+}
+
+static counter counter_static;
+static counter counter_anonymous_namespace;
+static counter counter_extern;
+static counter counter_function_local;
+static bool errors (false);
+
+static std::string
+all_counters ()
+{
+  return to_string (counter_static)
+    + ' ' + to_string (counter_anonymous_namespace)
+    + ' ' + to_string (counter_extern)
+    + ' ' + to_string (counter_function_local);
+}
+
+static void
+check_counters (const char *name, const char *expected)
+{
+  std::string actual{all_counters ()};
+  if (actual != expected)
+    {
+      printf ("error: %s: (%s) != (%s)\n",
+              name, actual.c_str (), expected);
+      errors = true;
+    }
+}
+
+static void
+reset_all ()
+{
+  counter_static.reset ();
+  counter_anonymous_namespace.reset ();
+  counter_extern.reset ();
+  counter_function_local.reset ();
+}
+
+static thread_local counting<&counter_static> counting_static;
+namespace {
+  thread_local counting<&counter_anonymous_namespace>
+    counting_anonymous_namespace;
+}
+extern thread_local counting<&counter_extern> counting_extern;
+thread_local counting<&counter_extern> counting_extern;
+
+static void *
+thread_without_access (void *)
+{
+  return nullptr;
+}
+
+static void *
+thread_with_access (void *)
+{
+  thread_local counting<&counter_function_local> counting_function_local;
+  counting_function_local.operation ();
+  check_counters ("early in thread_with_access", "0/0 0/0 0/0 1/0");
+  counting_static.operation ();
+  counting_anonymous_namespace.operation ();
+  counting_extern.operation ();
+  check_counters ("in thread_with_access", "1/0 1/0 1/0 1/0");
+  return nullptr;
+}
+
+static int
+do_test (void)
+{
+  std::function<void (void *(void *))> do_pthread =
+    [](void *(func) (void *))
+    {
+      pthread_t thr;
+      int ret = pthread_create (&thr, nullptr, func, nullptr);
+      if (ret != 0)
+        {
+          errno = ret;
+          printf ("error: pthread_create: %m\n");
+          errors = true;
+          return;
+        }
+      ret = pthread_join (thr, nullptr);
+      if (ret != 0)
+        {
+          errno = ret;
+          printf ("error: pthread_join: %m\n");
+          errors = true;
+          return;
+        }
+    };
+  std::function<void (void *(void *))> do_std_thread =
+    [](void *(func) (void *))
+    {
+      std::thread thr{[func] {func (nullptr);}};
+      thr.join ();
+    };
+
+  std::array<std::pair<const char *, std::function<void (void *(void *))>>, 2>
+    do_thread_X
+      {{
+        {"pthread_create", do_pthread},
+        {"std::thread", do_std_thread},
+      }};
+
+  for (auto do_thread : do_thread_X)
+    {
+      printf ("info: testing %s\n", do_thread.first);
+      check_counters ("initial", "0/0 0/0 0/0 0/0");
+      do_thread.second (thread_without_access);
+      check_counters ("after thread_without_access", "0/0 0/0 0/0 0/0");
+      reset_all ();
+      do_thread.second (thread_with_access);
+      check_counters ("after thread_with_access", "1/1 1/1 1/1 1/1");
+      reset_all ();
+    }
+
+  return errors;
+}
+
+#define TEST_FUNCTION do_test ()
+#include "../test-skeleton.c"
-- 
2.4.3