/*
 * Copyright 2010  OpenSourceCodeStewardshipFoundation
 *
 * Licensed under BSD
 */

#include <stdio.h>
#include <stdlib.h>

#include "VMS_impl/VMS.h"
#include "Vthread.h"
#include "Vthread_helper.h"
#include "C_Libraries/Queue_impl/PrivateQueue.h"
#include "C_Libraries/Hash_impl/PrivateHash.h"


//==========================================================================

void
Vthread__init();

void
Vthread__init_Seq();

void
Vthread__init_Helper();


//===========================================================================

/*These are the library functions *called in the application*
 * 
 */



//===========================================================================

inline int32
Vthread__giveMinWorkUnitCycles( float32 percentOverhead )
 {
   return MIN_WORK_UNIT_CYCLES;
 }

inline int32
Vthread__giveIdealNumWorkUnits()
 {
   return NUM_SCHED_SLOTS * NUM_CORES;
 }

inline int32
Vthread__give_number_of_cores_to_schedule_onto()
 {
   return NUM_CORES;
 }

/*For now, use TSC -- later, make these two macros with assembly that first
 * saves jump point, and second jumps back several times to get reliable time
 */
inline void
Vthread__start_primitive()
 { saveLowTimeStampCountInto( ((VthdSemEnv *)(_VMSMasterEnv->semanticEnv))->
                              primitiveStartTime );
 }

/*Just quick and dirty for now -- make reliable later
 * will want this to jump back several times -- to be sure cache is warm
 * because don't want comm time included in calc-time measurement -- and
 * also to throw out any "weird" values due to OS interrupt or TSC rollover
 */
inline int32
Vthread__end_primitive_and_give_cycles()
 { int32 endTime, startTime;
   //TODO: fix by repeating time-measurement
   saveLowTimeStampCountInto( endTime );
   startTime=((VthdSemEnv*)(_VMSMasterEnv->semanticEnv))->primitiveStartTime;
   return (endTime - startTime);
 }



//===========================================================================

/*Re-use this in the entry-point fn
 */
inline SlaveVP *
Vthread__create_slaveVP_helper( TopLevelFnPtr fnPtr, void *initData,
                          VthdSemEnv *semEnv,    int32 coreToScheduleOnto )
 { SlaveVP      *newSlv;
   VthdSemData   *semData;

      //This is running in master, so use internal version
   newSlv = VMS_WL__create_slaveVP( fnPtr, initData );

   semData = VMS_WL__malloc( sizeof(VthdSemData) );
   semData->highestTransEntered = -1;
   semData->lastTransEntered    = NULL;

   newSlv->semanticData = semData;

   //=================== Assign new processor to a core =====================
   #ifdef DEBUG__TURN_ON_SEQUENTIAL_MODE
   newSlv->coreAnimatedBy = 0;

   #else

   if(coreToScheduleOnto < 0 || coreToScheduleOnto >= NUM_CORES )
    {    //out-of-range, so round-robin assignment
      newSlv->coreAnimatedBy = semEnv->nextCoreToGetNewSlv;

      if( semEnv->nextCoreToGetNewSlv >= NUM_CORES - 1 )
          semEnv->nextCoreToGetNewSlv  = 0;
      else
          semEnv->nextCoreToGetNewSlv += 1;
    }
   else //core num in-range, so use it
    { newSlv->coreAnimatedBy = coreToScheduleOnto;
    }
   #endif
   //========================================================================

   return newSlv;
 }


/*
 */
inline SlaveVP *
Vthread__create_thread( TopLevelFnPtr fnPtr, void *initData,
                          SlaveVP *creatingSlv )
 { VthdSemReq  reqData;

      //the semantic request data is on the stack and disappears when this
      // call returns -- it's guaranteed to remain in the Slv's stack for as
      // long as the Slv is suspended.
   reqData.reqType            = 0; //know the type because is a VMS create req
   reqData.coreToScheduleOnto = -1; //means round-robin schedule
   reqData.fnPtr              = fnPtr;
   reqData.initData           = initData;
   reqData.requestingSlv       = creatingSlv;

   VMS_WL__send_create_slaveVP_req( &reqData, creatingSlv );

   return creatingSlv->dataRetFromReq;
 }


inline SlaveVP *
Vthread__create_thread_with_affinity( TopLevelFnPtr fnPtr, void *initData,
                           SlaveVP *creatingSlv,  int32  coreToScheduleOnto )
 { VthdSemReq  reqData;

      //the semantic request data is on the stack and disappears when this
      // call returns -- it's guaranteed to remain in the Slv's stack for as
      // long as the Slv is suspended.
   reqData.reqType            = 0; //know type because in a VMS create req
   reqData.coreToScheduleOnto = coreToScheduleOnto;
   reqData.fnPtr              = fnPtr;
   reqData.initData           = initData;
   reqData.requestingSlv       = creatingSlv;

   VMS_WL__send_create_slaveVP_req( &reqData, creatingSlv );
 }

inline void
Vthread__dissipate_thread( SlaveVP *procrToDissipate )
 {
   VMS_WL__send_dissipate_req( procrToDissipate );
 }


//===========================================================================

void *
Vthread__malloc( size_t sizeToMalloc, SlaveVP *animSlv )
 { VthdSemReq  reqData;

   reqData.reqType      = malloc_req;
   reqData.sizeToMalloc = sizeToMalloc;
   reqData.requestingSlv = animSlv;

   VMS_WL__send_sem_request( &reqData, animSlv );

   return animSlv->dataRetFromReq;
 }


/*Sends request to Master, which does the work of freeing
 */
void
Vthread__free( void *ptrToFree, SlaveVP *animSlv )
 { VthdSemReq  reqData;

   reqData.reqType      = free_req;
   reqData.ptrToFree    = ptrToFree;
   reqData.requestingSlv = animSlv;

   VMS_WL__send_sem_request( &reqData, animSlv );
 }


//===========================================================================

inline void
Vthread__set_globals_to( void *globals )
 {
   ((VthdSemEnv *)
    (_VMSMasterEnv->semanticEnv))->applicationGlobals = globals;
 }

inline void *
Vthread__give_globals()
 {
   return((VthdSemEnv *) (_VMSMasterEnv->semanticEnv))->applicationGlobals;
 }


//===========================================================================

inline int32
Vthread__make_mutex( SlaveVP *animSlv )
 { VthdSemReq  reqData;

   reqData.reqType      = make_mutex;
   reqData.requestingSlv = animSlv;

   VMS_WL__send_sem_request( &reqData, animSlv );

   return (int32)animSlv->dataRetFromReq; //mutexid is 32bit wide
 }

inline void
Vthread__mutex_lock( int32 mutexIdx, SlaveVP *acquiringSlv )
 { VthdSemReq  reqData;

   reqData.reqType      = mutex_lock;
   reqData.mutexIdx     = mutexIdx;
   reqData.requestingSlv = acquiringSlv;

   VMS_WL__send_sem_request( &reqData, acquiringSlv );
 }

inline void
Vthread__mutex_unlock( int32 mutexIdx, SlaveVP *releasingSlv )
 { VthdSemReq  reqData;

   reqData.reqType      = mutex_unlock;
   reqData.mutexIdx     = mutexIdx;
   reqData.requestingSlv = releasingSlv;

   VMS_WL__send_sem_request( &reqData, releasingSlv );
 }


//=======================
inline int32
Vthread__make_cond( int32 ownedMutexIdx, SlaveVP *animSlv)
 { VthdSemReq  reqData;

   reqData.reqType      = make_cond;
   reqData.mutexIdx     = ownedMutexIdx;
   reqData.requestingSlv = animSlv;

   VMS_WL__send_sem_request( &reqData, animSlv );

   return (int32)animSlv->dataRetFromReq; //condIdx is 32 bit wide
 }

inline void
Vthread__cond_wait( int32 condIdx, SlaveVP *waitingSlv)
 { VthdSemReq  reqData;

   reqData.reqType      = cond_wait;
   reqData.condIdx      = condIdx;
   reqData.requestingSlv = waitingSlv;

   VMS_WL__send_sem_request( &reqData, waitingSlv );
 }

inline void *
Vthread__cond_signal( int32 condIdx, SlaveVP *signallingSlv )
 { VthdSemReq  reqData;

   reqData.reqType      = cond_signal;
   reqData.condIdx      = condIdx;
   reqData.requestingSlv = signallingSlv;

   VMS_WL__send_sem_request( &reqData, signallingSlv );
 }


//===========================================================================
//
/*A function singleton is a function whose body executes exactly once, on a
 * single core, no matter how many times the fuction is called and no
 * matter how many cores or the timing of cores calling it.
 *
 *A data singleton is a ticket attached to data.  That ticket can be used
 * to get the data through the function exactly once, no matter how many
 * times the data is given to the function, and no matter the timing of
 * trying to get the data through from different cores.
 */

/*Fn singleton uses ID as index into array of singleton structs held in the
 * semantic environment.
 */
void
Vthread__start_fn_singleton( int32 singletonID,   SlaveVP *animSlv )
 {
   VthdSemReq  reqData;

      //
   reqData.reqType     = singleton_fn_start;
   reqData.singletonID = singletonID;

   VMS_WL__send_sem_request( &reqData, animSlv );
   if( animSlv->dataRetFromReq != 0 ) //addr of matching end-singleton
    {
      VthdSemEnv *semEnv = VMS_int__give_sem_env_for( animSlv ); //not protected!
      VMS_int__return_to_addr_in_ptd_to_loc(
                         &((semEnv->fnSingletons[singletonID]).savedRetAddr) );
    }
 }

/*Data singleton hands addr of loc holding a pointer to a singleton struct.
 * The start_data_singleton makes the structure and puts its addr into the
 * location.
 */
void
Vthread__start_data_singleton( VthdSingleton *singleton,  SlaveVP *animSlv )
 {
   VthdSemReq  reqData;

   if( singleton->savedRetAddr && singleton->hasFinished )
      goto JmpToEndSingleton;
      
   reqData.reqType       = singleton_data_start;
   reqData.singleton = singleton;

   VMS_WL__send_sem_request( &reqData, animSlv );
   if( animSlv->dataRetFromReq ) //either 0 or end singleton's return addr
    {    
       JmpToEndSingleton:
       VMS_int__return_to_addr_in_ptd_to_loc(&(singleton->savedRetAddr));
    }
   //now, simply return
   //will exit either from the start singleton call or the end-singleton call
 }

/*Uses ID as index into array of flags.  If flag already set, resumes from
 * end-label.  Else, sets flag and resumes normally.
 *
 *Note, this call cannot be inlined because the instr addr at the label
 * inside is shared by all invocations of a given singleton ID.
 */
void
Vthread__end_fn_singleton( int32 singletonID, SlaveVP *animSlv )
 {
   VthdSemReq  reqData;

   //don't need this addr until after at least one singleton has reached
   // this function
   VthdSemEnv *semEnv = VMS_int__give_sem_env_for( animSlv );
   VMS_int__return_to_addr_in_ptd_to_loc(
                         &((semEnv->fnSingletons[singletonID]).savedRetAddr) );

   reqData.reqType     = singleton_fn_end;
   reqData.singletonID = singletonID;

   VMS_WL__send_sem_request( &reqData, animSlv );
 }

void
Vthread__end_data_singleton( VthdSingleton *singleton, SlaveVP *animSlv )
 {
   VthdSemReq  reqData;

      //don't need this addr until after singleton struct has reached
      // this function for first time
      //do assembly that saves the return addr of this fn call into the
      // data singleton -- that data-singleton can only be given to exactly
      // one instance in the code of this function.  However, can use this
      // function in different places for different data-singletons.

   VMS_int__save_return_into_ptd_to_loc_then_do_ret(&(singleton->savedRetAddr));

   reqData.reqType    = singleton_data_end;
   reqData.singleton  = singleton;

   VMS_WL__send_sem_request( &reqData, animSlv );
 }


/*This executes the function in the masterVP, so it executes in isolation
 * from any other copies -- only one copy of the function can ever execute
 * at a time.
 *
 *It suspends to the master, and the request handler takes the function
 * pointer out of the request and calls it, then resumes the Slv.
 *Only very short functions should be called this way -- for longer-running
 * isolation, use transaction-start and transaction-end, which run the code
 * between as work-code.
 */
void
Vthread__animate_short_fn_in_isolation( PtrToAtomicFn ptrToFnToExecInMaster,
                                    void *data, SlaveVP *animSlv )
 {
   VthdSemReq  reqData;

      //
   reqData.reqType          = atomic;
   reqData.fnToExecInMaster = ptrToFnToExecInMaster;
   reqData.dataForFn        = data;

   VMS_WL__send_sem_request( &reqData, animSlv );
 }


/*This suspends to the master.
 *First, it looks at the Slv's data, to see the highest transactionID that Slv
 * already has entered.  If the current ID is not larger, it throws an
 * exception stating a bug in the code.  Otherwise it puts the current ID
 * there, and adds the ID to a linked list of IDs entered -- the list is
 * used to check that exits are properly ordered.
 *Next it is uses transactionID as index into an array of transaction
 * structures.
 *If the "Slv_currently_executing" field is non-null, then put requesting Slv
 * into queue in the struct.  (At some point a holder will request
 * end-transaction, which will take this Slv from the queue and resume it.)
 *If NULL, then write requesting into the field and resume.
 */
void
Vthread__start_transaction( int32 transactionID, SlaveVP *animSlv )
 {
   VthdSemReq  reqData;

      //
   reqData.reqType     = trans_start;
   reqData.transID     = transactionID;

   VMS_WL__send_sem_request( &reqData, animSlv );
 }

/*This suspends to the master, then uses transactionID as index into an
 * array of transaction structures.
 *It looks at Slv_currently_executing to be sure it's same as requesting Slv.
 * If different, throws an exception, stating there's a bug in the code.
 *Next it looks at the queue in the structure.
 *If it's empty, it sets Slv_currently_executing field to NULL and resumes.
 *If something in, gets it, sets Slv_currently_executing to that Slv, then
 * resumes both.
 */
void
Vthread__end_transaction( int32 transactionID, SlaveVP *animSlv )
 {
   VthdSemReq  reqData;

      //
   reqData.reqType     = trans_end;
   reqData.transID     = transactionID;

   VMS_WL__send_sem_request( &reqData, animSlv );
 }
//===========================================================================
