summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBruno Haible <bruno@clisp.org>2019-06-21 04:35:41 +0200
committerBruno Haible <bruno@clisp.org>2019-06-21 11:17:47 +0200
commita12c7fd184e8a8251b1a3e9c0f681873995ec204 (patch)
tree7cdfc5f60e90dc73697bfc970e4dce7e62b850f6
parentb6293339816d7df16bb4d31d2ec1c1e5a79f976e (diff)
downloadgnulib-a12c7fd184e8a8251b1a3e9c0f681873995ec204.tar.gz
gnulib-a12c7fd184e8a8251b1a3e9c0f681873995ec204.tar.bz2
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'.
-rw-r--r--ChangeLog20
-rw-r--r--doc/posix-headers/threads.texi14
-rw-r--r--lib/threads.in.h4
-rw-r--r--m4/threads.m484
-rw-r--r--modules/threads-h2
-rw-r--r--modules/threads-h-tests10
-rw-r--r--tests/test-thread_local.c196
-rw-r--r--tests/test-threads.c6
8 files changed, 319 insertions, 17 deletions
diff --git a/ChangeLog b/ChangeLog
index 7701108b77..85bfbcaba9 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,23 @@
+2019-06-21 Bruno Haible <bruno@clisp.org>
+
+ 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 <bruno@clisp.org>
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 <threads.h> facility is available.
+dnl Sets the variable LIBSTDTHREAD to the linker options for use in a Makefile
+dnl for a program that uses the <threads.h> 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: <https://gcc.gnu.org/onlinedocs/gcc-3.3.1/gcc/Thread-Local.html> */
+/* ARM C: <https://developer.arm.com/docs/dui0472/latest/compiler-specific-features/__declspecthread> */
+/* IBM C: supported only with compiler option -qtls, see
+ <https://www.ibm.com/support/knowledgecenter/SSGH2K_12.1.0/com.ibm.xlc121.aix.doc/compiler_ref/opt_tls.html> */
+/* Oracle Solaris Studio C: <https://docs.oracle.com/cd/E18659_01/html/821-1384/bjabr.html> */
+/* MSVC: <https://docs.microsoft.com/en-us/cpp/parallel/thread-local-storage-tls> */
+#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:
<threads.h>
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 <https://www.gnu.org/licenses/>. */
+
+/* Written by Bruno Haible <bruno@clisp.org>, 2005. */
+
+#include <config.h>
+
+#include <threads.h>
+
+#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 <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#if HAVE_DECL_ALARM
+# include <signal.h>
+# include <unistd.h>
+#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 <stdio.h>
+
+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 <threads.h>
-/* 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

Return to:

Send suggestions and report system problems to the System administrator.