From a12c7fd184e8a8251b1a3e9c0f681873995ec204 Mon Sep 17 00:00:00 2001 From: Bruno Haible Date: Fri, 21 Jun 2019 04:35:41 +0200 Subject: [PATCH] threads-h: Define 'thread_local' if and only it actually works. * m4/threads.m4 (gl_THREAD_LOCAL_DEFINITION): New macro. (gl_THREADS_H): Define _Thread_local to __thread also for ARM C, IBM C, Oracle Solaris Studio C. Compile a simple program, to see whether _Thread_local basically works. Set HAVE_THREAD_LOCAL and LIBTHREADLOCAL. (gl_THREADS_H_DEFAULTS): Initialize HAVE_THREAD_LOCAL. * lib/threads.in.h (thread_local): Undefine if it does not work. * modules/threads-h (Makefile.am): Substitute HAVE_THREAD_LOCAL. (Link): Mention LIBTHREADLOCAL. * tests/test-threads.c: Don't check that thread_local is defined. * tests/test-thread_local.c: New file. * modules/threads-h-tests (Files): Add it and macros.h. (Depends-on): Add thrd and stdint. (configure.ac): Test whether 'alarm' is declared. (Makefile.am): Arrange to build and link test-thread_local. * doc/posix-headers/threads.texi: Mention the platforms that don't support 'thread_local'. --- ChangeLog | 20 ++++ doc/posix-headers/threads.texi | 14 +++ lib/threads.in.h | 4 + m4/threads.m4 | 84 ++++++++++++-- modules/threads-h | 2 + modules/threads-h-tests | 10 +- tests/test-thread_local.c | 196 +++++++++++++++++++++++++++++++++ tests/test-threads.c | 6 +- 8 files changed, 319 insertions(+), 17 deletions(-) create mode 100644 tests/test-thread_local.c diff --git a/ChangeLog b/ChangeLog index 7701108b77..85bfbcaba9 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,23 @@ +2019-06-21 Bruno Haible + + threads-h: Define 'thread_local' if and only it actually works. + * m4/threads.m4 (gl_THREAD_LOCAL_DEFINITION): New macro. + (gl_THREADS_H): Define _Thread_local to __thread also for ARM C, IBM C, + Oracle Solaris Studio C. Compile a simple program, to see whether + _Thread_local basically works. Set HAVE_THREAD_LOCAL and LIBTHREADLOCAL. + (gl_THREADS_H_DEFAULTS): Initialize HAVE_THREAD_LOCAL. + * lib/threads.in.h (thread_local): Undefine if it does not work. + * modules/threads-h (Makefile.am): Substitute HAVE_THREAD_LOCAL. + (Link): Mention LIBTHREADLOCAL. + * tests/test-threads.c: Don't check that thread_local is defined. + * tests/test-thread_local.c: New file. + * modules/threads-h-tests (Files): Add it and macros.h. + (Depends-on): Add thrd and stdint. + (configure.ac): Test whether 'alarm' is declared. + (Makefile.am): Arrange to build and link test-thread_local. + * doc/posix-headers/threads.texi: Mention the platforms that don't + support 'thread_local'. + 2019-06-20 Bruno Haible threads-h: Simplify link dependencies. diff --git a/doc/posix-headers/threads.texi b/doc/posix-headers/threads.texi index 71ae43a48b..3c1c9fdc62 100644 --- a/doc/posix-headers/threads.texi +++ b/doc/posix-headers/threads.texi @@ -20,4 +20,18 @@ AIX 7.2. Portability problems not fixed by Gnulib: @itemize +@item +There is no way to define a working @code{thread_local} macro on some platforms: +@itemize +@item +Mac OS X 10.5, +@item +OpenBSD 6.5, +@item +AIX 7.1 with gcc (but it works with @samp{xlc -qthreaded -qtls}), +@item +HP-UX 11.31 with cc (but it works with gcc), +@item +IRIX 6.5. +@end itemize @end itemize diff --git a/lib/threads.in.h b/lib/threads.in.h index a18d64b4be..b7fed72257 100644 --- a/lib/threads.in.h +++ b/lib/threads.in.h @@ -67,6 +67,10 @@ #if !@HAVE_THREADS_H@ || !defined thread_local # define thread_local _Thread_local #endif +/* Define the macro thread_local if and only if it actually works. */ +#if !@HAVE_THREAD_LOCAL@ +# undef thread_local +#endif /* =========== ISO C 11 7.26.5 Thread functions =========== */ diff --git a/m4/threads.m4 b/m4/threads.m4 index 87e97f3a0b..1aebb0ab1c 100644 --- a/m4/threads.m4 +++ b/m4/threads.m4 @@ -1,9 +1,14 @@ -# threads.m4 serial 3 +# threads.m4 serial 4 dnl Copyright (C) 2019 Free Software Foundation, Inc. dnl This file is free software; the Free Software Foundation dnl gives unlimited permission to copy and/or distribute it, dnl with or without modifications, as long as this notice is preserved. +dnl Tests whether the facility is available. +dnl Sets the variable LIBSTDTHREAD to the linker options for use in a Makefile +dnl for a program that uses the functions. +dnl Sets the variable LIBTHREADLOCAL to the linker options for use in a Makefile +dnl for a program that uses the 'thread_local' macro. AC_DEFUN([gl_THREADS_H], [ AC_REQUIRE([gl_THREADS_H_DEFAULTS]) @@ -79,16 +84,54 @@ AC_DEFUN([gl_THREADS_H], esac AC_SUBST([LIBSTDTHREAD]) - AH_VERBATIM([thread_local], -[/* The _Thread_local keyword of C11. */ -#ifndef _Thread_local -# if defined __GNUC__ -# define _Thread_local __thread -# elif defined _MSC_VER -# define _Thread_local __declspec (thread) -# endif -#endif -]) + dnl Define _Thread_local. + dnl GCC, for example, supports '__thread' since version 3.3, but it supports + dnl '_Thread_local' only starting with version 4.9. + AH_VERBATIM([thread_local], gl_THREAD_LOCAL_DEFINITION) + + dnl Test whether _Thread_local is supported in the compiler and linker. + AC_CACHE_CHECK([whether _Thread_local works], + [gl_cv_thread_local_works], + [dnl On AIX 7.1 with GCC 4.8.1, this test program compiles fine, but the + dnl 'test-thread-local' test misbehaves. + dnl On Android 4.3, this test program compiles fine, but the + dnl 'test-thread-local' test crashes. + if case "$host_os" in + aix*) test -n "$GCC" ;; + linux*-android*) true ;; + *) false ;; + esac + then + gl_cv_thread_local_works="guessing no" + else + AC_COMPILE_IFELSE( + [AC_LANG_PROGRAM([gl_THREAD_LOCAL_DEFINITION[ + int _Thread_local x; + ]], [[ + x = 42; + ]])], + [gl_cv_thread_local_works=yes], + [gl_cv_thread_local_works=no]) + fi + ]) + case "$gl_cv_thread_local_works" in + *yes) ;; + *) HAVE_THREAD_LOCAL=0 ;; + esac + + dnl Determine the link dependencies of '_Thread_local'. + LIBTHREADLOCAL= + dnl On AIX 7.2 with "xlc -qthreaded -qtls", programs that use _Thread_local + dnl as defined above produce link errors regarding the symbols + dnl '.__tls_get_mod' and '__tls_get_addr'. Similarly, on AIX 7.2 with gcc, + dnl 32-bit programs that use _Thread_local produce link errors regarding the + dnl symbol '__get_tpointer'. The fix is to link with -lpthread. + case "$host_os" in + aix*) + LIBTHREADLOCAL=-lpthread + ;; + esac + AC_SUBST([LIBTHREADLOCAL]) dnl Check for declarations of anything we want to poison if the dnl corresponding gnulib module is not in use, and which is not @@ -102,6 +145,24 @@ AC_DEFUN([gl_THREADS_H], tss_create tss_delete tss_get tss_set]) ]) +dnl Expands to C preprocessor statements that define _Thread_local. +AC_DEFUN([gl_THREAD_LOCAL_DEFINITION], +[[/* The _Thread_local keyword of C11. */ +/* GNU C: */ +/* ARM C: */ +/* IBM C: supported only with compiler option -qtls, see + */ +/* Oracle Solaris Studio C: */ +/* MSVC: */ +#ifndef _Thread_local +# if defined __GNUC__ || defined __CC_ARM || defined __xlC__ || defined __SUNPRO_C +# define _Thread_local __thread +# elif defined _MSC_VER +# define _Thread_local __declspec (thread) +# endif +#endif +]]) + AC_DEFUN([gl_THREADS_MODULE_INDICATOR], [ dnl Use AC_REQUIRE here, so that the default settings are expanded once only. @@ -118,6 +179,7 @@ AC_DEFUN([gl_THREADS_H_DEFAULTS], GNULIB_THRD=0; AC_SUBST([GNULIB_THRD]) GNULIB_TSS=0; AC_SUBST([GNULIB_TSS]) dnl Assume proper GNU behavior unless another module says otherwise. + HAVE_THREAD_LOCAL=1; AC_SUBST([HAVE_THREAD_LOCAL]) BROKEN_THRD_START_T=0; AC_SUBST([BROKEN_THRD_START_T]) REPLACE_THRD_CREATE=0; AC_SUBST([REPLACE_THRD_CREATE]) REPLACE_THRD_CURRENT=0; AC_SUBST([REPLACE_THRD_CURRENT]) diff --git a/modules/threads-h b/modules/threads-h index 238955c6b5..c5af53e812 100644 --- a/modules/threads-h +++ b/modules/threads-h @@ -50,6 +50,7 @@ threads.h: threads.in.h $(top_builddir)/config.status $(CXXDEFS_H) $(_NORETURN_H -e 's/@''GNULIB_MTX''@/$(GNULIB_MTX)/g' \ -e 's/@''GNULIB_THRD''@/$(GNULIB_THRD)/g' \ -e 's/@''GNULIB_TSS''@/$(GNULIB_TSS)/g' \ + -e 's|@''HAVE_THREAD_LOCAL''@|$(HAVE_THREAD_LOCAL)|g' \ -e 's|@''BROKEN_THRD_START_T''@|$(BROKEN_THRD_START_T)|g' \ -e 's|@''REPLACE_THRD_CREATE''@|$(REPLACE_THRD_CREATE)|g' \ -e 's|@''REPLACE_THRD_CURRENT''@|$(REPLACE_THRD_CURRENT)|g' \ @@ -65,6 +66,7 @@ Include: Link: +$(LIBTHREADLOCAL) if you use the thread_local macro License: LGPLv2+ diff --git a/modules/threads-h-tests b/modules/threads-h-tests index e4f399d6df..abbd43aca5 100644 --- a/modules/threads-h-tests +++ b/modules/threads-h-tests @@ -1,12 +1,18 @@ Files: tests/test-threads.c +tests/test-thread_local.c +tests/macros.h Depends-on: threads-h-c++-tests +thrd +stdint configure.ac: +AC_CHECK_DECLS_ONCE([alarm]) Makefile.am: -TESTS += test-threads -check_PROGRAMS += test-threads +TESTS += test-threads test-thread_local +check_PROGRAMS += test-threads test-thread_local test_threads_LDADD = $(LDADD) @LIBSTDTHREAD@ +test_thread_local_LDADD = $(LDADD) @LIBSTDTHREAD@ @LIBTHREADLOCAL@ diff --git a/tests/test-thread_local.c b/tests/test-thread_local.c new file mode 100644 index 0000000000..fddf455a5d --- /dev/null +++ b/tests/test-thread_local.c @@ -0,0 +1,196 @@ +/* Test of thread-local storage in multithreaded situations. + Copyright (C) 2005, 2008-2019 Free Software Foundation, Inc. + + This program 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 of the License, or + (at your option) any later version. + + This program 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. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +/* Written by Bruno Haible , 2005. */ + +#include + +#include + +#ifdef thread_local + +/* Whether to help the scheduler through explicit yield(). + Uncomment this to see if the operating system has a fair scheduler. */ +#define EXPLICIT_YIELD 1 + +/* Whether to print debugging messages. */ +#define ENABLE_DEBUGGING 0 + +/* Number of simultaneous threads. */ +#define THREAD_COUNT 16 + +/* Number of operations performed in each thread. */ +#define REPEAT_COUNT 50000 + +#include +#include +#include + +#if HAVE_DECL_ALARM +# include +# include +#endif + +#include "macros.h" + +#if ENABLE_DEBUGGING +# define dbgprintf printf +#else +# define dbgprintf if (0) printf +#endif + +#if EXPLICIT_YIELD +# define yield() thrd_yield () +#else +# define yield() +#endif + +/* Returns a reference to the current thread as a pointer, for debugging. */ +#if defined __MVS__ + /* On IBM z/OS, pthread_t is a struct with an 8-byte '__' field. + The first three bytes of this field appear to uniquely identify a + pthread_t, though not necessarily representing a pointer. */ +# define thrd_current_pointer() (*((void **) thrd_current ().__)) +#elif defined __sun + /* On Solaris, thrd_t is merely an 'unsigned int'. */ +# define thrd_current_pointer() ((void *) (uintptr_t) thrd_current ()) +#else +# define thrd_current_pointer() ((void *) thrd_current ()) +#endif + +static void +perhaps_yield (void) +{ + /* Call yield () only with a certain probability, otherwise the + sequence of thread activations may be too predictable. */ + if ((((unsigned int) rand () >> 3) % 4) == 0) + yield (); +} + + +/* ----------------------- Test thread-local storage ----------------------- */ + +#define KEYS_COUNT 4 +static unsigned int thread_local value0; +static unsigned int thread_local value1; +static unsigned int thread_local value2; +static unsigned int thread_local value3; + +static int +worker_thread (void *arg) +{ + unsigned int id = (unsigned int) (uintptr_t) arg; + int i, j, repeat; + unsigned int *values[KEYS_COUNT] = { &value0, &value1, &value2, &value3 }; + + dbgprintf ("Worker %p started\n", thrd_current_pointer ()); + + /* Initialize the per-thread storage. */ + dbgprintf ("Worker %p before first assignment\n", thrd_current_pointer ()); + for (i = 0; i < KEYS_COUNT; i++) + { + *values[i] = (((unsigned int) rand () >> 3) % 1000000) * THREAD_COUNT + id; + /* Hopefully no arithmetic overflow. */ + if ((*values[i] % THREAD_COUNT) != id) + abort (); + } + dbgprintf ("Worker %p after first assignment\n", thrd_current_pointer ()); + perhaps_yield (); + + /* Shuffle around the pointers. */ + for (repeat = REPEAT_COUNT; repeat > 0; repeat--) + { + dbgprintf ("Worker %p doing value swapping\n", thrd_current_pointer ()); + i = ((unsigned int) rand () >> 3) % KEYS_COUNT; + j = ((unsigned int) rand () >> 3) % KEYS_COUNT; + if (i != j) + { + unsigned int vi = *values[i]; + unsigned int vj = *values[j]; + + *values[i] = vj; + *values[j] = vi; + } + perhaps_yield (); + } + + /* Verify that all the values are from this thread. */ + dbgprintf ("Worker %p before final verify\n", thrd_current_pointer ()); + for (i = 0; i < KEYS_COUNT; i++) + if ((*values[i] % THREAD_COUNT) != id) + abort (); + dbgprintf ("Worker %p after final verify\n", thrd_current_pointer ()); + perhaps_yield (); + + dbgprintf ("Worker %p dying.\n", thrd_current_pointer ()); + return 0; +} + +static void +test_thread_local (void) +{ + int pass, i; + + for (pass = 0; pass < 2; pass++) + { + thrd_t threads[THREAD_COUNT]; + + /* Spawn the threads. */ + for (i = 0; i < THREAD_COUNT; i++) + ASSERT (thrd_create (&threads[i], worker_thread, (void *) (uintptr_t) i) + == thrd_success); + + /* Wait for the threads to terminate. */ + for (i = 0; i < THREAD_COUNT; i++) + ASSERT (thrd_join (threads[i], NULL) == thrd_success); + } +} + + +/* -------------------------------------------------------------------------- */ + +int +main () +{ +#if HAVE_DECL_ALARM + /* Declare failure if test takes too long, by using default abort + caused by SIGALRM. */ + int alarm_value = 600; + signal (SIGALRM, SIG_DFL); + alarm (alarm_value); +#endif + + printf ("Starting test_thread_local ..."); fflush (stdout); + test_thread_local (); + printf (" OK\n"); fflush (stdout); + + return 0; +} + +#else + +/* No thread-local storage support available in the compiler and linker. */ + +#include + +int +main () +{ + fputs ("Skipping test: thread_local not supported\n", stderr); + return 77; +} + +#endif diff --git a/tests/test-threads.c b/tests/test-threads.c index 39a0f3c41c..716fceb9ba 100644 --- a/tests/test-threads.c +++ b/tests/test-threads.c @@ -20,10 +20,8 @@ #include -/* Check that thread_local is defined. */ -#ifndef thread_local -"oops" -#endif +/* Don't check that thread_local is defined. + We cannot define it properly on some platforms. */ /* Check that ONCE_FLAG_INIT is defined. */ #ifndef ONCE_FLAG_INIT -- 2.39.5