/*
** vim:ts=4 sw=4 expandtab
*/
/*
** Copyright (C) 2007-2009 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.
*/

/*
** mercury_stm.h - runtime support for software transactional memory.
*/

#ifndef MERCURY_STM_H
#define MERCURY_STM_H

#include "mercury_types.h"
#include "mercury_thread.h"
#include "mercury_conf.h"
#include "mercury_conf_param.h"
#include "mercury_context.h"
#include "mercury_engine.h"
#include <stdio.h>

typedef struct MR_STM_Waiter_Struct         MR_STM_Waiter;
typedef struct MR_STM_Var_Struct            MR_STM_Var;
typedef struct MR_STM_TransRecord_Struct    MR_STM_TransRecord;
typedef struct MR_STM_TransLog_Struct       MR_STM_TransLog;


/*
** The type MR_ThreadId provides an abstract means of identifying a Mercury
** thread. Depending upon the grade we use one of three notions of thread
** identity.
**
** For high-level code with parallelism it is the value returned by a call
** to pthread_self().
**
** For high-level code without parallelism it is an MR_Integer - in this case
** concurrency is not supported so there is only ever one thread.
**
** For low-level code with use the context address as the thread id.
**
** The macro MR_THIS_THREAD_ID expands to a value of type MR_ThreadId.
** This value is the identity of the current thread.
*/
#if defined(MR_HIGHLEVEL_CODE)

    #if defined(MR_THREAD_SAFE)
        typedef pthread_t       MR_ThreadId;

        #define MR_THIS_THREAD_ID pthread_self()
    #else
        typedef MR_Integer      MR_ThreadId;
        /*
        ** Since these grades don't support concurrency there is only one
        ** thread which we always give the id 0.
        */
        #define MR_THIS_THREAD_ID 0
    #endif

#else /* !MR_HIGHLEVEL_CODE */

    typedef MR_Context      *MR_ThreadId;
    #define MR_THIS_THREAD_ID (MR_ENGINE(MR_eng_this_context))

#endif /* !MR_HIGHLEVEL_CODE */


/*
** The type MR_STM_ConditionVar provides an abstract method of blocking and
** signalling threads based on conditions.
*/
#if defined(MR_HIGHLEVEL_CODE)

    #if defined(MR_THREAD_SAFE)
        typedef MercuryCond  MR_STM_ConditionVar;

        #define MR_STM_condvar_init(x)        pthread_cond_init(x, MR_COND_ATTR)
        #define MR_STM_condvar_wait(x, y)     MR_cond_wait(x, y, "STM_condvar_wait")
        #define MR_STM_condvar_signal(x)      MR_cond_signal(x, "STM_condvar_signal")
    #else
        typedef MR_Integer      MR_STM_ConditionVar;
        /*
        ** Since these grades don't support concurrency, there is no
        ** need to block the thread.
        */
        #define MR_STM_condvar_init(x)
        #define MR_STM_condvar_wait(x, y)
        #define MR_STM_condvar_signal(x)
    #endif

#else /* !MR_HIGHLEVEL_CODE */

    typedef MR_STM_TransLog  MR_STM_ConditionVar;

    /*
    ** NOTE: The global STM lock (MR_STM_lock) must be held when calling this
    ** function, as it manipulates the STM variable waiter lists.
    */
    void MR_STM_condvar_signal(MR_STM_ConditionVar *cvar);

    #define MR_STM_context_from_condvar(x)      ((x)->MR_STM_tl_thread)

#endif /* !MR_HIGHLEVEL_CODE */

/*
** A waiter is the identity of a thread that is blocking until the value
** of this transaction variable changes.
*/

struct MR_STM_Waiter_Struct {
    MR_STM_ConditionVar *MR_STM_cond_var;
    MR_STM_Waiter       *MR_STM_waiter_next;
    MR_STM_Waiter       *MR_STM_waiter_prev;
};

/*
** XXX This should also contain the type_info for the value, so we can
** print them out in the debugger.
*/

struct MR_STM_Var_Struct {
    MR_Word         MR_STM_var_value;
    MR_STM_Waiter   *MR_STM_var_waiters;
};

struct MR_STM_TransRecord_Struct {
    MR_STM_Var          *MR_STM_tr_var;
    MR_Word             MR_STM_tr_old_value;
    MR_Word             MR_STM_tr_new_value;
    MR_STM_TransRecord  *MR_STM_tr_next;
};

struct MR_STM_TransLog_Struct {
    MR_STM_TransRecord  *MR_STM_tl_records;
    MR_ThreadId         MR_STM_tl_thread;
    MR_STM_TransLog     *MR_STM_tl_parent;
};

/*
** The global STM lock. This lock must be acquired before validating or
** committing a transaction log.
*/

#if defined(MR_THREAD_SAFE)
    extern MercuryLock  MR_STM_lock;
#endif

/*
** Allocate a new transaction variable.
*/
#define MR_STM_new_stm_var(value, var)                                  \
    do {                                                                \
        (var) = MR_GC_NEW_ATTRIB(MR_STM_Var, MR_ALLOC_SITE_STM);        \
        (var)->MR_STM_var_value = (value);                              \
        (var)->MR_STM_var_waiters = NULL;                               \
    } while (0)

/*
** Create a new transaction log.
** If the log is for a nested transaction then the `parent' field points
** to the log of the enclosing transaction. It is NULL otherwise.
*/
#define MR_STM_create_log(tlog, parent)                                 \
    do {                                                                \
        (tlog) = MR_GC_NEW_ATTRIB(MR_STM_TransLog, MR_ALLOC_SITE_STM);  \
        (tlog)->MR_STM_tl_records = NULL;                               \
        (tlog)->MR_STM_tl_thread = MR_THIS_THREAD_ID;                   \
        (tlog)->MR_STM_tl_parent = (parent);                            \
    } while (0)

/*
** Discard a transaction log.
** XXX we should free the memory in nogc grades.
*/
#define MR_STM_discard_log(tlog)                                        \
    do {                                                                \
        (tlog) = NULL;                                                  \
    } while (0)

/*
** Record a change of state for transaction variable `var' in the
** given transaction log. `old_value' and `new_value' give the value
** of the transaction variable before and after the change of state.
*/

extern  void        MR_STM_record_transaction(MR_STM_TransLog *tlog,
                        MR_STM_Var *var,
                        MR_Word old_value, MR_Word new_value);

/*
** Add a waiter for the current thread to all of the transaction variables
** listed in the log.
*/

extern  void        MR_STM_wait(MR_STM_TransLog *tlog,
                        MR_STM_ConditionVar *cvar);

/*
** Detach waiters for the current thread from all of the transaction variables
** referenced by the given transaction log.
*/

extern  void        MR_STM_unwait(MR_STM_TransLog *tlog,
                        MR_STM_ConditionVar *cvar);

/*
** Attach a waiter for thread tid to the transaction variable. The condition
** variable should be a condition variable properly initialised and associated
** with the thread.
*/
extern  void        MR_STM_attach_waiter(MR_STM_Var *var, MR_ThreadId tid,
                        MR_STM_ConditionVar *cvar);

/*
** Detach any waiters for thread tid from the transaction variable.
** This will cause execution to abort if no waiter for thread tid can
** be found since it can only correctly be called in a situation where
** such a waiter exists.
*/

extern  void        MR_STM_detach_waiter(MR_STM_Var *var,
                        MR_STM_ConditionVar *cvar);

extern  MR_Integer  MR_STM_validate(MR_STM_TransLog *tlog);

/*
** Irrevocably write the changes stored in a transaction log to memory.
*/

extern  void        MR_STM_commit(MR_STM_TransLog *tlog);

/*
** Changes the value of transaction variable var in a transaction log.
*/

extern  void        MR_STM_write_var(MR_STM_Var *var, MR_Word value,
                        MR_STM_TransLog *tlog);

/*
** Returns the value of transaction variable var in a transaction log.
** If no entry for var exists, the actual value of the transaction variable
** var is returned (and added to the transaction log).
*/

extern  MR_Word     MR_STM_read_var(MR_STM_Var *var, MR_STM_TransLog *tlog);

/*
** Changes the value of the transaction variable var without going through
** the log.
**
** NOTE: This functions must only be used for debugging purposes and will
** eventually be removed. Please, DO NOT use it for normal operations.
*/

extern  void        MR_STM_unsafe_write_var(MR_STM_Var *var, MR_Word value);

/*
** Blocks a thread from execution. This method is called by the thread
** which is to be blocked. The STM lock MUST be aquired by the thread
** before this method is called and acquires the lock when the thread
** is signalled.
*/

extern  void        MR_STM_block_thread(MR_STM_TransLog *tlog);

/*
** Merges a transaction log with its parent. Do not merge it with any
** other ancestors. Aborts if the given transaction log does not have a
** parent.
*/

extern  void        MR_STM_merge_transactions(MR_STM_TransLog *tlog);

/*
** Reschedules all threads currently waiting on the given transaction
** variables.
*/

extern  void        MR_STM_signal_vars(MR_STM_Var *tvar);

/*
** These definitions need to be kept in sync with the definition of the type
** stm_validation_result/0 in library/stm_builtin.m. Changes here may need
** be reflected there.
*/

#define MR_STM_TRANSACTION_VALID 0
#define MR_STM_TRANSACTION_INVALID 1

#endif /* not MERCURY_STM_H */
