/*
** vim: ts=4 sw=4 expandtab
*/
/*
** Copyright (C) 1997-1998, 2000, 2003, 2005-2007, 2009-2011 The University of Melbourne.
** This file may only be copied under the terms of the GNU Library General
** Public License - see the file COPYING.LIB in the Mercury distribution.
*/
#ifndef MERCURY_THREAD_H
#define MERCURY_THREAD_H

#include "mercury_std.h"

#ifdef  MR_THREAD_SAFE

  #include <signal.h>   /* for sigset_t on the SPARC */
  #include <pthread.h>
  #include <semaphore.h> /* POSIX semaphores */

  #if defined(MR_DIGITAL_UNIX_PTHREADS)
    #define MR_MUTEX_ATTR       pthread_mutexattr_default
    #define MR_COND_ATTR        pthread_condattr_default
    #define MR_THREAD_ATTR      pthread_attr_default
  #else
    #define MR_MUTEX_ATTR       NULL
    #define MR_COND_ATTR        NULL
    #define MR_THREAD_ATTR      NULL
  #endif

  typedef pthread_t         MercuryThread;
  typedef pthread_key_t     MercuryThreadKey;
  typedef pthread_mutex_t   MercuryLock;
  typedef pthread_cond_t    MercuryCond;

  typedef sem_t             MercurySem;

extern int
MR_mutex_lock(MercuryLock *lock, const char *from);
extern int
MR_mutex_unlock(MercuryLock *lock, const char *from);
extern int
MR_cond_signal(MercuryCond *cond, const char *from);
extern int
MR_cond_broadcast(MercuryCond *cond, const char *from);
extern int
MR_cond_wait(MercuryCond *cond, MercuryLock *lock, const char *from);
extern int
MR_cond_timed_wait(MercuryCond *cond, MercuryLock *lock,
    const struct timespec *abstime, const char *from);

extern int
MR_sem_wait(MercurySem *sem, const char *from);
extern int
MR_sem_post(MercurySem *sem, const char *from);

   #if defined(MR_PTHREADS_WIN32)
extern MercuryThread
MR_null_thread(void);
  #else
    #define MR_null_thread() ((MercuryThread) 0)
  #endif

  #define MR_thread_equal(a, b)       pthread_equal((a), (b))

  #if defined(MR_PTHREADS_WIN32)
    #define MR_SELF_THREAD_ID ((MR_Integer) pthread_self().p)
  #else
    #define MR_SELF_THREAD_ID ((MR_Integer) pthread_self())
  #endif

  extern MR_bool    MR_debug_threads;

  #ifndef MR_DEBUG_THREADS
    /*
    ** The following macros should be used once the use of locking
    ** in the generated code is considered stable, since the alternative
    ** versions do the same thing, but with debugging support.
    */
    #define MR_LOCK(lck, from)      pthread_mutex_lock((lck))
    #define MR_UNLOCK(lck, from)    pthread_mutex_unlock((lck))

    #define MR_SIGNAL(cnd, from)    pthread_cond_signal((cnd))
    #define MR_BROADCAST(cnd, from) pthread_cond_broadcast((cnd))
    #define MR_WAIT(cnd, mtx, from) pthread_cond_wait((cnd), (mtx))
    #define MR_TIMED_WAIT(cond, mtx, abstime, from)                         \
        pthread_cond_timedwait((cond), (mtx), (abstime))

    #define MR_SEM_POST(sem, from)  sem_post((sem))
    #define MR_SEM_WAIT(sem, from)  sem_wait((sem))
  #else
    #define MR_LOCK(lck, from)                          \
                ( MR_debug_threads ?                    \
                    MR_mutex_lock((lck), (from))        \
                :                                       \
                    pthread_mutex_lock((lck))           \
                )
    #define MR_UNLOCK(lck, from)                        \
                ( MR_debug_threads ?                    \
                    MR_mutex_unlock((lck), (from))      \
                :                                       \
                    pthread_mutex_unlock((lck))         \
                )

    #define MR_SIGNAL(cnd, from)                        \
                ( MR_debug_threads ?                    \
                    MR_cond_signal((cnd), (from))       \
                :                                       \
                    pthread_cond_signal((cnd))          \
                )
    #define MR_BROADCAST(cnd, from)                     \
                ( MR_debug_threads ?                    \
                    MR_cond_broadcast((cnd), (from))    \
                :                                       \
                    pthread_cond_broadcast((cnd))       \
                )
    #define MR_WAIT(cnd, mtx, from)                     \
                ( MR_debug_threads ?                    \
                    MR_cond_wait((cnd), (mtx), (from))  \
                :                                       \
                    pthread_cond_wait((cnd), (mtx))     \
                )
    #define MR_TIMED_WAIT(cond, mtx, abstime, from)                         \
        ( MR_debug_threads ?                                                \
            MR_cond_timed_wait((cond), (mtx), (abstime), (from))            \
        :                                                                   \
            pthread_cond_timedwait((cond), (mtx), (abstime))                \
        )

    #define MR_SEM_WAIT(sem, from)                      \
        ( MR_debug_threads ?                            \
            MR_sem_wait((sem), (from))                  \
        :                                               \
            sem_wait((sem))                             \
        )

    #define MR_SEM_POST(sem, from)                      \
        ( MR_debug_threads ?                            \
            MR_sem_post((sem), (from))                  \
        :                                               \
            sem_post((sem))                             \
        )

  #endif

    /*
    ** The following two macros are used to protect pragma c_code
    ** predicates which are not thread-safe.
    ** See the comments below.
    */
  #define MR_OBTAIN_GLOBAL_LOCK(where)  MR_LOCK(&MR_global_lock, (where))
  #define MR_RELEASE_GLOBAL_LOCK(where) MR_UNLOCK(&MR_global_lock, (where))

  #if defined(MR_DIGITAL_UNIX_PTHREADS)
    #define MR_GETSPECIFIC(key)                         \
        ({                                              \
            pthread_addr_t gstmp;                       \
            pthread_getspecific((key), &gstmp);         \
            (void *) gstmp;                             \
        })
    #define MR_KEY_CREATE   pthread_keycreate
  #else
    #define MR_GETSPECIFIC(key) pthread_getspecific((key))
    #define MR_KEY_CREATE       pthread_key_create
  #endif

  typedef struct {
      void  (*func)(void *);
      void  *arg;
  } MR_ThreadGoal;

  /*
  ** create_thread(Goal) creates a new POSIX thread, and creates and
  ** initializes a new Mercury engine to run in that thread. If Goal
  ** is a NULL pointer, that thread will suspend on the global Mercury
  ** runqueue. If Goal is non-NULL, it is a pointer to a MR_ThreadGoal
  ** structure containing a function and an argument. The function will
  ** be called with the given argument in the new thread.
  */

  extern MercuryThread      *MR_create_thread(MR_ThreadGoal *);
  extern void               MR_destroy_thread(void *eng);

  /*
  ** The primordial thread. Currently used for debugging.
  */
  extern MercuryThread      MR_primordial_thread;

  /*
  ** MR_global_lock is a mutex for ensuring that only one non-threadsafe
  ** piece of pragma c code executes at a time. If `not_threadsafe' is
  ** given or `threadsafe' is not given in the attributes of a pragma
  ** c code definition of a predicate, then the generated code will
  ** obtain this lock before executing the C code fragment, and then
  ** release it afterwards.
  ** XXX we should emit a warning if may_call_mercury and not_threadsafe
  ** (the defaults) are specified since if you obtain the lock then
  ** call back into Mercury deadlock could result.
  */
  extern MercuryLock        MR_global_lock;

  #ifndef MR_HIGHLEVEL_CODE
  /*
  ** This lock protects writes to the MR_all_engine_bases structure.
  */
  extern MercuryLock        MR_init_engine_array_lock;
  #endif

  /*
  ** MR_exception_handler_key stores a key which can be used to get
  ** the current exception handler for the current thread.
  */
  extern MercuryThreadKey   MR_exception_handler_key;

#else /* not MR_THREAD_SAFE */

  #define MR_LOCK(nothing, from)        do { } while (0)
  #define MR_UNLOCK(nothing, from)      do { } while (0)

  #define MR_SIGNAL(nothing, from)      do { } while (0)
  #define MR_BROADCAST(nothing, from)   do { } while (0)
  #define MR_WAIT(no, thing, from)      (0)

  #define MR_OBTAIN_GLOBAL_LOCK(where)  do { } while (0)
  #define MR_RELEASE_GLOBAL_LOCK(where) do { } while (0)

#endif

/*
** These are used to prevent the process terminating as soon as the original
** Mercury thread terminates.
*/
extern MR_Integer MR_thread_barrier_count;
#ifdef MR_THREAD_SAFE
  extern MercuryLock MR_thread_barrier_lock;
  #ifdef MR_HIGHLEVEL_CODE
    extern MercuryCond MR_thread_barrier_cond;
  #endif
#endif
#ifndef MR_HIGHLEVEL_CODE
  extern struct MR_Context_Struct *MR_thread_barrier_context;
#endif

/*
** The following enum is used as the argument to init_thread.
** MR_use_now should be passed to init_thread to indicate that
** it has been called in a context in which it should initialize
** the current thread's environment and return.
** MR_use_later should be passed to indicate that the thread should
** be initialized, then suspend waiting for work to appear in the
** runqueue. The engine is destroyed when the execution of work from
** the runqueue returns.
*/

typedef enum { MR_use_now, MR_use_later } MR_when_to_use;

/*
** Create and initialize a new Mercury engine running in the current
** POSIX thread.
**
** See the comments above for the meaning of the argument.
** If there is already a Mercury engine running in the current POSIX
** thread then init_thread is just a no-op.
**
** Returns MR_TRUE if a Mercury engine was created as a result of this
** call *and* it is the caller's responsibility to finalize it (it is
** intended that the caller can store the return value and call
** finalize_thread_engine if it is true).
*/
extern MR_bool
MR_init_thread(MR_when_to_use);

/*
** Finalize the thread engine running in the current POSIX thread.
** This will release the resources used by this thread -- this is very
** important because the memory used for the det stack for each thread
** can be re-used by the next init_thread.
*/
extern void
MR_finalize_thread_engine(void);

/*
** The values of thread-local mutables are stored in an array per Mercury
** thread.  This makes it easy for a newly spawned thread to inherit (copy)
** all the thread-local mutables of its parent thread.
** Accesses to the array are protected by a mutex, in case a parallel
** conjunctions tries to read a thread-local value while another parallel
** conjunction (in the same Mercury thread) is writing to it.
**
** Each thread-local mutable has an associated index into the array, which is
** allocated to it during initialisation.  For ease of implementation there is
** an arbitrary limit to the number of thread-local mutables that are allowed.
*/
typedef struct MR_ThreadLocalMuts MR_ThreadLocalMuts;

struct MR_ThreadLocalMuts {
  #ifdef MR_THREAD_SAFE
    MercuryLock     MR_tlm_lock;
  #endif
    MR_Word         *MR_tlm_values;
};

#define MR_MAX_THREAD_LOCAL_MUTABLES    128
extern MR_Unsigned MR_num_thread_local_mutables;

/*
** Allocate an index into the thread-local mutable array for a mutable.
*/
extern MR_Unsigned
MR_new_thread_local_mutable_index(void);

/*
** Allocate a thread-local mutable array.
*/
extern MR_ThreadLocalMuts *
MR_create_thread_local_mutables(MR_Unsigned numslots);

/*
** Make a copy of a thread-local mutable array.
*/
extern MR_ThreadLocalMuts *
MR_clone_thread_local_mutables(const MR_ThreadLocalMuts *old_muts);

#define MR_THREAD_LOCAL_MUTABLES                                        \
    (MR_ENGINE(MR_eng_this_context)->MR_ctxt_thread_local_mutables)

#define MR_SET_THREAD_LOCAL_MUTABLES(tlm)                               \
    do {                                                                \
        MR_THREAD_LOCAL_MUTABLES = (tlm);                               \
    } while (0)

#define MR_get_thread_local_mutable(type, var, mut_index)               \
    do {                                                                \
        MR_ThreadLocalMuts  *tlm;                                       \
                                                                        \
        tlm = MR_THREAD_LOCAL_MUTABLES;                                 \
        MR_LOCK(&tlm->MR_tlm_lock, "MR_get_thread_local_mutable");      \
        var = * ((type *) &tlm->MR_tlm_values[(mut_index)]);            \
        MR_UNLOCK(&tlm->MR_tlm_lock, "MR_get_thread_local_mutable");    \
    } while (0)

#define MR_set_thread_local_mutable(type, var, mut_index)               \
    do {                                                                \
        MR_ThreadLocalMuts  *tlm;                                       \
                                                                        \
        tlm = MR_THREAD_LOCAL_MUTABLES;                                 \
        MR_LOCK(&tlm->MR_tlm_lock, "MR_set_thread_local_mutable");      \
        * ((type *) &tlm->MR_tlm_values[(mut_index)]) = (var);          \
        MR_UNLOCK(&tlm->MR_tlm_lock, "MR_set_thread_local_mutable");    \
    } while (0)

/*
** Initialise some static structures in mercury_thread.c
*/
void
MR_init_thread_stuff(void);

#endif  /* MERCURY_THREAD_H */
