/*
 * Copyright (c) 2006-2011 High Performance Computing Center Stuttgart, 
 *                         University of Stuttgart.  All rights reserved.
 * Author: Rainer Keller, HLRS
 *
 * Thread-local storage (TLS) is not supported on all environments.
 * This header file and test-program shows how to abstract away, using either
 *   __thread,
 *   __declspec(thread) or
 *   Pthread-Keys
 * depending on the (configure-set) CPP-variables HAVE___THREA, HAVE_DECLSPEC.
 *
 * Use the macros TLS_DECLARE, and the getters and setters TLS_GET and TLS_GET
 * to work on the declared variables.
 *
 * In case of PThread keys,  we need to resolve to using keys!
 * In order to do so, we need to declare and access
 * TLS variables through three macros:
 *  - TLS_DECLARE
 *  - TLS_SET and
 *  - TLS_GET
 * We do depend on the following (GCC-)extension:
 *  - In case of function-local static functions,
 *    we declare a sub-function to create a specific key.
 * Unfortunately, we do NOT use the following extensions:
 *  - Using typeof, we could get rid of the type-declaration
 *    which is used for casting, however typeof is not ANSI C.
 *  - We do NOT allow something like
 *       func (a, TLS_SET(int, my_var, 5));
 *    as we do not use the gcc-extension of returning macro-values.
 */

#include <assert.h>
#include <pthread.h>

#ifdef HAVE___THREAD

#  define TLS_DECLARE(type,name)   __thread type name
#  define TLS_SET(type,name,value) name = (value)
#  define TLS_GET(type,name)       (name)
#  define TLS_FREE(name)

#elif defined(HAVE_DECLSPEC_THREAD)

#  define TLS_DECLARE(type,name)   __declspec(thread) type name
#  define TLS_SET(type,name,value) name = (value)
#  define TLS_GET(type,name)       (name)
#  define TLS_FREE(name)

#else


#  define TLS_DECLARE(type,name)                                             \
   static pthread_key_t _##name##_key;                                       \
   static void _##name##_key_delete(void * arg)                              \
   {                                                                         \
     assert (NULL != arg);                                                   \
     free (arg);                                                             \
   }                                                                         \
   static void _##name##_key_create(void)                                    \
   {                                                                         \
     int _ret;                                                               \
     _ret = pthread_key_create(&(_##name##_key), _##name##_key_delete);      \
     _ret = _ret; /* To get rid of warnings in case of NDEBUG */             \
     assert (0 == _ret);                                                     \
   }                                                                         \
   static pthread_once_t _##name##_once = PTHREAD_ONCE_INIT

#  define TLS_SET(type,name,value)                                           \
   do {                                                                      \
     void * _ptr;                                                            \
     (void) pthread_once(&(_##name##_once), _##name##_key_create);           \
     if ((_ptr = pthread_getspecific(_##name##_key)) == NULL) {              \
       _ptr = malloc(sizeof(type));                                          \
       assert (NULL != _ptr);                                                \
       (void) pthread_setspecific(_##name##_key, _ptr);                      \
     }                                                                       \
     *((type*)_ptr) = (value);                                               \
   } while(0)

#  define TLS_GET(type,name)                                                 \
     *((type*)pthread_getspecific(_##name##_key))

#  define TLS_FREE(name)                                                     \
     pthread_key_delete (_##name##_key);

#endif


