bug-gnulib
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [PATCH] ISO C 11 threads implementation


From: Bruno Haible
Subject: Re: [PATCH] ISO C 11 threads implementation
Date: Fri, 21 Jun 2019 11:45:55 +0200
User-agent: KMail/5.1.3 (Linux/4.4.0-145-generic; KDE/5.18.0; x86_64; ; )

This patch makes the 'thread_local' storage class actually usable on
many platforms.

I found that 'thread_local' works on the following platforms:
  * Linux with glibc
    on i386/x86_64/x32 (even on "old" systems such as CentOS 5), mips/mips64,
       sparc/sparc64, alpha, arm, arm64, powerpc/powerpc64, ia64, s390/s390x,
       riscv64, m68k, hppa
  * Linux with musl libc
    on i386/x86_64
  * GNU/kFreeBSD
    on i386/x86_64
  * Hurd
    on i386
  * Mac OS X 10.13
    on x86_64
  * FreeBSD 11 and MidnightBSD 0.8
    on i386/x86_64, arm64
  * NetBSD 7
    on i386/x86_64
  * AIX 7 (with 'xlc -qthreaded -qtls')
  * AIX 7.2 (with gcc)
  * HP-UX 11.31 (with gcc)
  * Solaris 10
    on i386/x86_64, sparc/sparc64
  * Solaris 11.4
    on i386/x86_64
  * illumos (OpenIndiana)
    on i386/x86_64
  * Cygwin
    on i386/x86_64
  * mingw
    on i386/x86_64
  * Haiku
    on i386

It does not work (__thread not understood by the compiler and linker)
on the following platforms:
  * Mac OS X 10.5
    on i386/x86_64, powerpc
  * OpenBSD 6.5
  * HP-UX 11.31 (with cc)
  * IRIX 6.5 (with gcc)

It does not work (__thread not understood by the compiler and linker
but the test program crashes) on the following platforms:
  * AIX 7.1 (with gcc)
  * Android 4.3


2019-06-21  Bruno Haible  <address@hidden>

        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'.

diff --git a/doc/posix-headers/threads.texi b/doc/posix-headers/threads.texi
index 71ae43a..d3bc66f 100644
--- a/doc/posix-headers/threads.texi
+++ b/doc/posix-headers/threads.texi
@@ -20,4 +20,20 @@ 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,
+@item
+Android 4.3.
+@end itemize
 @end itemize
diff --git a/lib/threads.in.h b/lib/threads.in.h
index a18d64b..b7fed72 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 87e97f3..1aebb0a 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 238955c..c5af53e 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 e4f399d..abbd43a 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 0000000..fddf455
--- /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 <address@hidden>, 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 39a0f3c..716fceb 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




reply via email to

[Prev in Thread] Current Thread [Next in Thread]