/*
vim: ft=c ts=4 sw=4 et
*/
/*
** Copyright (C) 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.
*/

#include "mercury_types.h"
#include "mercury_par_builtin.h"

/***************************************************************************
**
** Futures for decedent AND-parallelism..
**
***************************************************************************/

#ifdef MR_CONSERVATIVE_GC

void
MR_finalize_future(void *obj, void *cd)
{
    MR_Future *future;

    future = (MR_Future *) obj;

  #ifdef MR_THREAD_SAFE
    pthread_mutex_destroy(&(future->MR_fut_lock));
  #endif
}

#endif

/***************************************************************************
**
** Code for Loop controls..
**
***************************************************************************/

#if defined(MR_THREAD_SAFE) && defined(MR_LL_PARALLEL_CONJ)

MR_LoopControl *
MR_lc_create(unsigned num_workers)
{
    MR_LoopControl  *lc;
    unsigned        i;

    lc = MR_GC_malloc(sizeof(MR_LoopControl) +
        (num_workers-1) * sizeof(MR_LoopControlSlot));
    lc->MR_lc_num_slots = num_workers;
    for (i = 0; i < num_workers; i++) {
        /*
        ** We allocate contexts as necessary, so that we never allocate a
        ** context we don't use.  Also, by allocating the contexts in
        ** MR_lc_spawn_off, already spawned off computations can run in
        ** parallel with the allocation of contexts for computations that are
        ** about to be spawned off.
        */
        lc->MR_lc_slots[i].MR_lcs_context = NULL;
        lc->MR_lc_slots[i].MR_lcs_is_free = MR_TRUE;
    }
    lc->MR_lc_outstanding_workers = 0;
    lc->MR_lc_master_context_lock = MR_US_LOCK_INITIAL_VALUE;
    lc->MR_lc_master_context = NULL;
    lc->MR_lc_finished = MR_FALSE;
    lc->MR_lc_free_slot_hint = 0;

#ifdef MR_DEBUG_LOOP_CONTROL
    fprintf(stderr, "lc_create(%d) -> %p)\n", num_workers, lc);
#endif

    return lc;
}

/*
** Deprecated, this was part of our old loop control design.
*/
MR_Bool
MR_lc_try_get_free_slot(MR_LoopControl *lc, MR_Unsigned *lcs_idx)
{
    if (lc->MR_lc_outstanding_workers == lc->MR_lc_num_slots) {
        return MR_FALSE;
    } else {
        unsigned hint, offset, i;

        /*
        ** We start indexing into the array starting at this hint, it is either
        ** set to a known free slot or the next unchecked slot after finding a
        ** free slot.
        */
        hint = lc->MR_lc_free_slot_hint;

        for (offset = 0; offset < lc->MR_lc_num_slots; offset++) {
            i = (hint + offset) % lc->MR_lc_num_slots;
            if (lc->MR_lc_slots[i].MR_lcs_is_free) {
                lc->MR_lc_slots[i].MR_lcs_is_free = MR_FALSE;
                lc->MR_lc_free_slot_hint = (i+1) % lc->MR_lc_num_slots;
                MR_atomic_inc_int(&(lc->MR_lc_outstanding_workers));
                *lcs_idx = i;
                return MR_TRUE;
            }
        }

        return MR_FALSE;
    }
}

void
MR_lc_spawn_off_func(MR_LoopControl *lc, MR_Unsigned lcs_idx, MR_Code
        *code_ptr)
{
    MR_LoopControlSlot *lcs = &(lc->MR_lc_slots[lcs_idx]);

#if MR_DEBUG_LOOP_CONTROL
    fprintf(stderr, "lc_spawn_off(%p, %d, %p) sp: %p\n",
        lc, lcs_idx, code_ptr, MR_sp);
#endif

    lcs->MR_lcs_context->MR_ctxt_resume = code_ptr;
    lcs->MR_lcs_context->MR_ctxt_parent_sp = MR_sp;
    MR_schedule_context(lcs->MR_lcs_context);
}

void
MR_lc_join(MR_LoopControl *lc, MR_Unsigned lcs_idx)
{
    MR_LoopControlSlot  *lcs;
    MR_bool             last_worker;
    MR_Context          *wakeup_context;

    lcs = &(lc->MR_lc_slots[lcs_idx]);

#ifdef MR_DEBUG_LOOP_CONTROL
    fprintf(stderr, "lc_join(%p, %d)\n", lc, lcs_idx);
#endif

    lcs->MR_lcs_is_free = MR_TRUE;
    lc->MR_lc_free_slot_hint = lcs_idx;
    /* Ensure the slot is free before we perform the decrement. */
    MR_CPU_SFENCE;

    /*
    ** We have to determine if we're either the last of first workers to
    ** finish.  To do this we cannot use atomic decrment since we need to do
    ** more than one comparison, Therefore we use a CAS.
    */
    last_worker =
        MR_atomic_dec_and_is_zero_int(&(lc->MR_lc_outstanding_workers));

    /*
    ** If this is the last worker to finish, then take the lock before checking
    ** the master context feild, otherwise we might race and end up never
    ** resuming the master.
    */
    if (last_worker) {
        MR_US_SPIN_LOCK(&(lc->MR_lc_master_context_lock));
        /*
        ** Don't read the master field until after we have the lock
        */
        MR_CPU_MFENCE;
    }

    /*
    ** If the master thread is suspended, wake it up, provided that either:
    ** - the loop has finished and this is the last worker to exit, or
    ** - the loop has not finished (so the master can create more work).
    */
    if ((lc->MR_lc_master_context != NULL) &&
        ((lc->MR_lc_finished && last_worker) || (!lc->MR_lc_finished)))
    {
        /*
        ** Now take a lock and re-read the master context field.
        */
        if (!last_worker) {
            MR_US_SPIN_LOCK(&(lc->MR_lc_master_context_lock));
            /*
            ** Don't read the master field until after we have the lock
            */
            MR_CPU_MFENCE;
        }
        wakeup_context = lc->MR_lc_master_context;
        lc->MR_lc_master_context = NULL;
        MR_CPU_SFENCE; /* Make sure the field to nULL */
        MR_US_UNLOCK(&(lc->MR_lc_master_context_lock));
        if (wakeup_context != NULL) {
#ifdef MR_DEBUG_LOOP_CONTROL
            fprintf(stderr, "Waking up master\n");
#endif
            /*
            ** XXX: it is faster to switch to this context ourselves
            ** since we are going to unload our own context.
            ** Or we should switch to another worker context if there is one.
            */
            MR_schedule_context(wakeup_context);
        }
    } else if (last_worker) {
        MR_US_UNLOCK(&(lc->MR_lc_master_context_lock));
    }
}

#endif /* MR_THREAD_SAFE && MR_LL_PARALLEL_CONJ */

