/*
** 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.c - runtime support for software transactional memory. */

#include "mercury_std.h"
#include "mercury_stm.h"
#include "mercury_memory.h"
#include "mercury_misc.h"

#if defined(MR_THREAD_SAFE)
MercuryLock MR_STM_lock;
#endif

void
MR_STM_record_transaction(MR_STM_TransLog *tlog, MR_STM_Var *var,
    MR_Word old_value, MR_Word new_value)
{
    MR_STM_TransRecord  *new_record;

    new_record = MR_GC_NEW_ATTRIB(MR_STM_TransRecord,
        MR_ALLOC_SITE_RUNTIME);
    new_record->MR_STM_tr_var = var;
    new_record->MR_STM_tr_old_value = old_value;
    new_record->MR_STM_tr_new_value = new_value;
    new_record->MR_STM_tr_next = tlog->MR_STM_tl_records;
    tlog->MR_STM_tl_records = new_record;
}

void
MR_STM_attach_waiter(MR_STM_Var *var, MR_ThreadId tid,
    MR_STM_ConditionVar *cvar)
{
    MR_STM_Waiter   *new_waiter;

    new_waiter = MR_GC_NEW_ATTRIB(MR_STM_Waiter, MR_ALLOC_SITE_RUNTIME);
    new_waiter->MR_STM_cond_var = cvar;

    if (var->MR_STM_var_waiters == NULL) {
        var->MR_STM_var_waiters = new_waiter;
        new_waiter->MR_STM_waiter_prev = NULL;
        new_waiter->MR_STM_waiter_next = NULL;
    } else {
        new_waiter->MR_STM_waiter_prev = NULL;
        new_waiter->MR_STM_waiter_next = var->MR_STM_var_waiters;
        var->MR_STM_var_waiters->MR_STM_waiter_prev = new_waiter;
        var->MR_STM_var_waiters = new_waiter;
    }
}

void
MR_STM_detach_waiter(MR_STM_Var *var, MR_STM_ConditionVar *cvar)
{
    MR_STM_Waiter   *curr_waiter;

    MR_assert(var != NULL);
    MR_assert(var->MR_STM_var_waiters != NULL);

    curr_waiter = var->MR_STM_var_waiters;
    while (curr_waiter != NULL) {
        if (curr_waiter->MR_STM_cond_var == cvar) {
            if (curr_waiter == var->MR_STM_var_waiters) {
                var->MR_STM_var_waiters =
                        var->MR_STM_var_waiters->MR_STM_waiter_next;
            }
            if (curr_waiter->MR_STM_waiter_prev != NULL) {
                curr_waiter->MR_STM_waiter_prev->MR_STM_waiter_next =
                        curr_waiter->MR_STM_waiter_next;
            }
            if (curr_waiter->MR_STM_waiter_next != NULL) {
                curr_waiter->MR_STM_waiter_next->MR_STM_waiter_prev =
                        curr_waiter->MR_STM_waiter_prev;
            }
            curr_waiter = NULL;
            return;
        }
        curr_waiter = curr_waiter->MR_STM_waiter_next;
    }

    MR_fatal_error("MR_STM_detach_waiter: Thread ID not in wait queue");
}

MR_Integer
MR_STM_validate(MR_STM_TransLog *tlog)
{
    MR_STM_TransRecord  *current;

    MR_assert(tlog != NULL);

#if defined(MR_STM_DEBUG)
    fprintf(stderr, "STM VALIDATE: validating log <0x%.8lx>\n",
        (MR_Word) tlog);
    fprintf(stderr, "\tRecords: <0x%.8lx>\n",
        (MR_Word) tlog->MR_STM_tl_records);
#endif

    while (tlog != NULL) {
        current = tlog->MR_STM_tl_records;

        while (current != NULL) {
            if (current->MR_STM_tr_var->MR_STM_var_value !=
                current->MR_STM_tr_old_value)
            {
#if defined(MR_STM_DEBUG)
                fprintf(stderr, "\ttransaction INVALID.\n");
#endif
                return MR_STM_TRANSACTION_INVALID;
            }

            current = current->MR_STM_tr_next;
        }

        tlog = tlog->MR_STM_tl_parent;
    }

#if defined(MR_STM_DEBUG)
    fprintf(stderr, "\ttransaction VALID.\n");
#endif

    return MR_STM_TRANSACTION_VALID;
}

void
MR_STM_signal_vars(MR_STM_Var *tvar)
{
    MR_STM_Waiter   *wait_queue;

    wait_queue = tvar->MR_STM_var_waiters;

    while (wait_queue != NULL) {
#if defined(MR_STM_DEBUG)
        fprintf(stderr, "STM SIGNAL: signalling log <0x%.8lx>\n",
            (MR_Word) wait_queue->MR_STM_cond_var);
#endif
        MR_STM_condvar_signal(wait_queue->MR_STM_cond_var);
        wait_queue = wait_queue->MR_STM_waiter_next;
    }
}

void
MR_STM_commit(MR_STM_TransLog *tlog)
{
    MR_STM_TransRecord  *current;

#if defined(MR_STM_DEBUG)
    fprintf(stderr, "STM COMMIT: committing log <0x%.8lx>\n",
        (MR_Word) tlog);
#endif

    current = tlog->MR_STM_tl_records;
    while (current != NULL) {
#if defined(MR_STM_DEBUG)
        fprintf(stderr,
            "\tSTM_Var <%.8lx>, changing value from %ld to %ld\n",
            (MR_Word) current->MR_STM_tr_var,
            current->MR_STM_tr_var->MR_STM_var_value,
            current->MR_STM_tr_new_value);
#endif
        current->MR_STM_tr_var->MR_STM_var_value =
            current->MR_STM_tr_new_value;

        MR_STM_signal_vars(current->MR_STM_tr_var);
        current = current->MR_STM_tr_next;
    }
}

void
MR_STM_wait(MR_STM_TransLog *tlog, MR_STM_ConditionVar *cvar)
{
    MR_STM_TransRecord  *current;
    MR_ThreadId         this_thread_id;

    this_thread_id = MR_THIS_THREAD_ID;

    current = tlog->MR_STM_tl_records;
    while (current != NULL) {
#if defined(MR_STM_DEBUG)
        fprintf(stderr, "STM WAIT: attaching waiter on log <0x%.8lx>\n",
            (MR_Word) tlog);
        fprintf(stderr, "\tSTM_Var: <0x%.8lx>\n",
            (MR_Word) current->MR_STM_tr_var);
#endif

        MR_STM_attach_waiter(current->MR_STM_tr_var, this_thread_id, cvar);
        current = current->MR_STM_tr_next;
    }
}

void
MR_STM_unwait(MR_STM_TransLog *tlog, MR_STM_ConditionVar *cvar)
{
    MR_STM_TransRecord  *current;
    MR_ThreadId         this_thread_id;

    this_thread_id = MR_THIS_THREAD_ID;
    current = tlog->MR_STM_tl_records;

    while (current != NULL) {
#if defined(MR_STM_DEBUG)
        fprintf(stderr, "STM UNWAIT: detaching waiter on log <0x%.8lx>\n",
            (MR_Word) tlog);
        fprintf(stderr, "\tSTM_Var: <0x%.8lx>\n",
            (MR_Word) current->MR_STM_tr_var);
#endif

        MR_STM_detach_waiter(current->MR_STM_tr_var, cvar);
        current = current->MR_STM_tr_next;
    }
}

void
MR_STM_unsafe_write_var(MR_STM_Var *var, MR_Word value)
{
#if defined(MR_STM_DEBUG)
    fprintf(stderr, "UNSAFE_WRITE_VAR:\n");
    fprintf(stderr, "\tSTM_Var <%.8lx>, changing value from %ld to %ld\n",
        (MR_Word) var, var->MR_STM_var_value, value);
#endif

    var->MR_STM_var_value = value;
}


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

    MR_STM_TransRecord  *current;
    MR_STM_TransRecord  *local_log;
    MR_bool             has_existing_record = MR_FALSE;

    /*
    ** Check to see if this transaction variable has an existing record in
    ** transaction log; if so, update it.
    */
    current = tlog->MR_STM_tl_records;
    while (current != NULL) {
        if (current->MR_STM_tr_var == var) {
            has_existing_record = MR_TRUE;
            current->MR_STM_tr_new_value = value;
            break;
        }

        current = current->MR_STM_tr_next;
    }

    /*
    ** Add a new entry for the transaction variable if didn't already
    ** have one.
    */
    if (!has_existing_record) {
        MR_STM_record_transaction(tlog, var, var->MR_STM_var_value, value);
    }
}

MR_Word
MR_STM_read_var(MR_STM_Var *var, MR_STM_TransLog *tlog)
{
    MR_STM_TransLog     *current_tlog;
    MR_STM_TransRecord  *current;

    current_tlog = tlog;

#if defined(MR_STM_DEBUG)
    fprintf(stderr, "STM Read: Log <%.8lx> -- var <%.8lx>\n",
        (MR_Word) tlog, (MR_Word) var);
#endif

    while (current_tlog != NULL) {
        current = current_tlog->MR_STM_tl_records;
        while (current != NULL) {
            if (current->MR_STM_tr_var == var) {
                return current->MR_STM_tr_new_value;
            }

            current = current->MR_STM_tr_next;
        }

        current_tlog = current_tlog->MR_STM_tl_parent;
    }

    /*
    ** We will only get to this point if the transaction variable does not
    ** currently have a record in the log or one of the enclosing logs,
    ** i.e. if this is the first time that its value has been read during
    ** this atomic scope.
    ** Add an entry that indicates that it has been read and then return
    ** the value that is stored in the transaction variable.
    */
    MR_STM_record_transaction(tlog, var, var->MR_STM_var_value,
        var->MR_STM_var_value);

    return var->MR_STM_var_value;
}

void
MR_STM_merge_transactions(MR_STM_TransLog *tlog)
{
    MR_STM_TransLog     *parent_log;
    MR_STM_TransRecord  *parent_current;
    MR_STM_TransRecord  *current;
    MR_bool             found_tvar_in_parent;

    MR_assert(tlog != NULL);
    MR_assert(tlog->MR_STM_tl_parent != NULL);

    parent_log = tlog->MR_STM_tl_parent;

    current = tlog->MR_STM_tl_records;
    while (current != NULL) {
        found_tvar_in_parent = MR_NO;
        parent_current = parent_log->MR_STM_tl_records;

        while (parent_current != NULL) {
            if (current->MR_STM_tr_var == parent_current->MR_STM_tr_var) {
                parent_current->MR_STM_tr_new_value =
                    current->MR_STM_tr_new_value;
                found_tvar_in_parent = MR_YES;
                break;
            }

            parent_current = parent_current->MR_STM_tr_next;
        }

        if (! found_tvar_in_parent) {
            MR_STM_record_transaction(parent_log,
                current->MR_STM_tr_var, current->MR_STM_tr_old_value,
                current->MR_STM_tr_new_value);
        }

        current = current->MR_STM_tr_next;
    }

#if defined(MR_STM_DEBUG)
    fprintf(stderr, "STM: Merging log end: <0x%.8lx>\n",
        (MR_Word) tlog);
#endif

    /* Deallocate child log */
#if !defined(MR_CONSERVATIVE_GC)
    /* XXX -- Free tlog and log entries */
#endif
}


#if defined(MR_HIGHLEVEL_CODE)
/*
** MR_STM_block_thread is called to block the thread in high level C grades,
** using POSIX thread facilities, as there is a POSIX thread for every 
** Mercury thread in these grades. The low level C grade equivalent of this
** code is defined in the stm_builtin library module.
*/
void
MR_STM_block_thread(MR_STM_TransLog *tlog)
{
#if defined(MR_THREAD_SAFE)
        MR_STM_ConditionVar     *thread_condvar;

        thread_condvar = MR_GC_NEW_ATTRIB(MR_STM_ConditionVar,
            MR_ALLOC_SITE_RUNTIME);
        MR_STM_condvar_init(thread_condvar);

        MR_STM_wait(tlog, thread_condvar);

#if defined(MR_STM_DEBUG)
        fprintf(stderr, "STM BLOCKING: log <0x%.8lx>\n", (MR_Word)tlog);
#endif
        MR_STM_condvar_wait(thread_condvar, &MR_STM_lock);

#if defined(MR_STM_DEBUG)
        fprintf(stderr, "STM RESCHEDULING: log <0x%.8lx>\n", (MR_Word)tlog);
#endif
        MR_STM_unwait(tlog, thread_condvar);

        MR_UNLOCK(&MR_STM_lock, "MR_STM_block_thread");

        MR_GC_free_attrib(thread_condvar);
#else
    MR_fatal_error("Blocking thread in non-parallel grade");
#endif
}
#endif  /* MR_HIGHLEVEL_CODE */


#if !defined(MR_HIGHLEVEL_CODE)
/*
** In the low level C grades, the "condition variable" created when an STM
** transaction blocks is actually a pointer to the transaction log.
** "Signalling" it consists of going through the STM variables listed in the
** log and removing the waiters attached to them for the context listed
** in the log. After this, the context can be safely rescheduled.
*/
void
MR_STM_condvar_signal(MR_STM_ConditionVar *cvar)
{
    /*
    ** Calling MR_STM_unwait here should be safe, as this signalling is called
    ** in response to a commit, while the committing thread holds the global
    ** STM lock. Note that a MR_STM_ConditionVar IS a MR_STM_TransLog if
    ** MR_HIGHLEVEL_CODE is not defined, which is why cvar is passed twice.
    */
    MR_STM_unwait(cvar, cvar);

#if defined(MR_STM_DEBUG)
        fprintf(stderr, "STM RESCHEDULING: log <0x%.8lx>\n", (MR_Word)cvar);
#endif

    MR_schedule_context(MR_STM_context_from_condvar(cvar));
}

#endif  /* !MR_HIGHLEVEL_CODE */
