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

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

#include "Queue_impl/PrivateQueue.h"
#include "Hash_impl/PrivateHash.h"

#include "VOMP.h"
#include "VOMP_Counter_Recording.h"

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

void
VOMP__init();

void
VOMP__init_Helper();
//==========================================================================



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


/*These are the library functions *called in the application*
 * 
 *There's a pattern for the outside sequential code to interact with the
 * VMS_HW code.
 *The VMS_HW system is inside a boundary..  every VOMP system is in its
 * own directory that contains the functions for each of the processor types.
 * One of the processor types is the "seed" processor that starts the
 * cascade of creating all the processors that do the work.
 *So, in the directory is a file called "EntryPoint.c" that contains the
 * function, named appropriately to the work performed, that the outside
 * sequential code calls.  This function follows a pattern:
 *1) it calls VOMP__init()
 *2) it creates the initial data for the seed processor, which is passed
 *    in to the function
 *3) it creates the seed VOMP processor, with the data to start it with.
 *4) it calls startVOMPThenWaitUntilWorkDone
 *5) it gets the returnValue from the transfer struc and returns that
 *    from the function
 *
 *For now, a new VOMP system has to be created via VOMP__init every
 * time an entry point function is called -- later, might add letting the
 * VOMP system be created once, and let all the entry points just reuse
 * it -- want to be as simple as possible now, and see by using what makes
 * sense for later..
 */



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

/*This is the "border crossing" function -- the thing that crosses from the
 * outside world, into the VMS_HW world.  It initializes and starts up the
 * VMS system, then creates one processor from the specified function and
 * puts it into the readyQ.  From that point, that one function is resp.
 * for creating all the other processors, that then create others, and so
 * forth.
 *When all the processors, including the seed, have dissipated, then this
 * function returns.  The results will have been written by side-effect via
 * pointers read from, or written into initData.
 *
 *NOTE: no Threads should exist in the outside program that might touch
 * any of the data reachable from initData passed in to here
 */
void
VOMP__create_seed_procr_and_do_work( TopLevelFnPtr fnPtr, void *initData )
 { VOMPSemEnv *semEnv;
   SlaveVP *seedPr;

   VOMP__init();      //normal multi-thd
   
   semEnv = _VMSMasterEnv->semanticEnv;

      //VOMP starts with one processor, which is put into initial environ,
      // and which then calls create() to create more, thereby expanding work
   seedPr = VOMP__create_procr_helper( fnPtr, initData,
                                      semEnv, semEnv->nextCoreToGetNewPr++ );

   resume_slaveVP( seedPr, semEnv );
   
   VMS_SS__start_the_work_then_wait_until_done();      //normal multi-thd

   VOMP__cleanup_after_shutdown();
 }


int32
VOMP__giveMinWorkUnitCycles( float32 percentOverhead )
 {
   return MIN_WORK_UNIT_CYCLES;
 }

int32
VOMP__giveIdealNumWorkUnits()
 {
   return NUM_ANIM_SLOTS * NUM_CORES;
 }

int32
VOMP__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
 */
void
VOMP__start_primitive()
 { saveLowTimeStampCountInto( ((VOMPSemEnv *)(_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
 */
int32
VOMP__end_primitive_and_give_cycles()
 { int32 endTime, startTime;
   //TODO: fix by repeating time-measurement
   saveLowTimeStampCountInto( endTime );
   startTime =((VOMPSemEnv*)(_VMSMasterEnv->semanticEnv))->primitiveStartTime;
   return (endTime - startTime);
 }

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

/*Initializes all the data-structures for a VOMP system -- but doesn't
 * start it running yet!
 *
 *This runs in the main thread -- before VMS starts up
 * 
 *This sets up the semantic layer over the VMS system
 *
 *First, calls VMS_Setup, then creates own environment, making it ready
 * for creating the seed processor and then starting the work.
 */
void
VOMP__init()
 {
   VMS_SS__init();
      //masterEnv, a global var, now is partially set up by init_VMS
      // after this, have VMS_int__malloc and VMS_int__free available

   VOMP__init_Helper();
 }


void idle_fn(void* data, SlaveVP *animatingSlv){
    while(1){
        VMS_int__suspend_slaveVP_and_send_req(animatingSlv);
    }
}

void
VOMP__init_Helper()
 { VOMPSemEnv       *semanticEnv;
   PrivQueueStruc **readyVPQs;
   int              coreIdx, i, j;
 
      //Hook up the semantic layer's plug-ins to the Master virt procr
   _VMSMasterEnv->requestHandler = &VOMP__Request_Handler;
   _VMSMasterEnv->slaveAssigner  = &VOMP__assign_slaveVP_to_slot;
   #ifdef HOLISTIC__TURN_ON_PERF_COUNTERS
   _VMSMasterEnv->counterHandler = &VOMP__counter_handler;
   #endif

      //create the semantic layer's environment (all its data) and add to
      // the master environment
   semanticEnv = VMS_int__malloc( sizeof( VOMPSemEnv ) );
   _VMSMasterEnv->semanticEnv = semanticEnv;
   
   #ifdef HOLISTIC__TURN_ON_PERF_COUNTERS
   VOMP__init_counter_data_structs();
   #endif
   semanticEnv->shutdownInitiated = FALSE;
   for(i=0;i<NUM_CORES;++i){
       for(j=0;j<NUM_ANIM_SLOTS;++j){
           semanticEnv->idlePr[i][j] = VMS_int__create_slaveVP(&idle_fn,NULL);
           semanticEnv->idlePr[i][j]->coreAnimatedBy = i;
       }
   }

   #ifdef HOLISTIC__TURN_ON_OBSERVE_UCC
   semanticEnv->unitList = makeListOfArrays(sizeof(Unit),128);
   semanticEnv->ctlDependenciesList = makeListOfArrays(sizeof(Dependency),128);
   semanticEnv->commDependenciesList = makeListOfArrays(sizeof(Dependency),128);
   semanticEnv->dynDependenciesList = makeListOfArrays(sizeof(Dependency),128);
   semanticEnv->ntonGroupsInfo = makePrivDynArrayOfSize((void***)&(semanticEnv->ntonGroups),8);
   
   semanticEnv->hwArcs = makeListOfArrays(sizeof(Dependency),128);
   memset(semanticEnv->last_in_slot,0,sizeof(NUM_CORES * NUM_ANIM_SLOTS * sizeof(Unit)));
   #endif

      //create the ready queue, hash tables used for pairing send to receive
      // and so forth
      //TODO: add hash tables for pairing sends with receives, and
      // initialize the data ownership system
   readyVPQs = VMS_int__malloc( NUM_CORES * sizeof(PrivQueueStruc *) );

   for( coreIdx = 0; coreIdx < NUM_CORES; coreIdx++ )
    {
      readyVPQs[ coreIdx ] = makeVMSQ();
    }
   
   semanticEnv->readyVPQs = readyVPQs;
   
   semanticEnv->nextCoreToGetNewPr = 0;
   semanticEnv->numSlaveVP = 0;
   
   semanticEnv->commHashTbl  = makeHashTable( 1<<16, &VMS_int__free );//start big

   //TODO: bug -- turn these arrays into dyn arrays to eliminate limit
   //semanticEnv->singletonHasBeenExecutedFlags = makeDynArrayInfo( );
   //semanticEnv->transactionStrucs = makeDynArrayInfo( );
   for( i = 0; i < NUM_STRUCS_IN_SEM_ENV; i++ )
    {
      semanticEnv->fnSingletons[i].endInstrAddr      = NULL;
      semanticEnv->fnSingletons[i].hasBeenStarted    = FALSE;
      semanticEnv->fnSingletons[i].hasFinished       = FALSE;
      semanticEnv->fnSingletons[i].waitQ             = makeVMSQ();
      semanticEnv->transactionStrucs[i].waitingVPQ   = makeVMSQ();
    }
 }


/*Frees any memory allocated by VOMP__init() then calls VMS_int__shutdown
 */
void
VOMP__cleanup_after_shutdown()
 { VOMPSemEnv *semanticEnv;
   
   semanticEnv = _VMSMasterEnv->semanticEnv;

   #ifdef HOLISTIC__TURN_ON_OBSERVE_UCC
   //UCC
   FILE* output;
   int n;
   char filename[255];    
    for(n=0;n<255;n++)
    {
        sprintf(filename, "./counters/UCC.%d",n);
        output = fopen(filename,"r");
        if(output)
        {
            fclose(output);
        }else{
            break;
        }
    }
   if(n<255){
    printf("Saving UCC to File: %s ...\n", filename);
    output = fopen(filename,"w+");
    if(output!=NULL){
        set_dependency_file(output);
        //fprintf(output,"digraph Dependencies {\n");
        //set_dot_file(output);
        //FIXME:  first line still depends on counters being enabled, replace w/ unit struct!
        //forAllInDynArrayDo(_VMSMasterEnv->counter_history_array_info, &print_dot_node_info );
        forAllInListOfArraysDo(semanticEnv->unitList, &print_unit_to_file);
        forAllInListOfArraysDo( semanticEnv->commDependenciesList, &print_comm_dependency_to_file );
        forAllInListOfArraysDo( semanticEnv->ctlDependenciesList, &print_ctl_dependency_to_file );
        forAllInDynArrayDo(semanticEnv->ntonGroupsInfo,&print_nton_to_file);
        //fprintf(output,"}\n");
        fflush(output);

    } else
        printf("Opening UCC file failed. Please check that folder \"counters\" exists in run directory and has write permission.\n");
   } else {
       printf("Could not open UCC file, please clean \"counters\" folder. (Must contain less than 255 files.)\n");
   }
   //Loop Graph
   for(n=0;n<255;n++)
    {
        sprintf(filename, "./counters/LoopGraph.%d",n);
        output = fopen(filename,"r");
        if(output)
        {
            fclose(output);
        }else{
            break;
        }
    }
   if(n<255){
    printf("Saving LoopGraph to File: %s ...\n", filename);
    output = fopen(filename,"w+");
    if(output!=NULL){
        set_dependency_file(output);
        //fprintf(output,"digraph Dependencies {\n");
        //set_dot_file(output);
        //FIXME:  first line still depends on counters being enabled, replace w/ unit struct!
        //forAllInDynArrayDo(_VMSMasterEnv->counter_history_array_info, &print_dot_node_info );
        forAllInListOfArraysDo( semanticEnv->unitList, &print_unit_to_file );
        forAllInListOfArraysDo( semanticEnv->commDependenciesList, &print_comm_dependency_to_file );
        forAllInListOfArraysDo( semanticEnv->ctlDependenciesList, &print_ctl_dependency_to_file );
        forAllInListOfArraysDo( semanticEnv->dynDependenciesList, &print_dyn_dependency_to_file );
        forAllInListOfArraysDo( semanticEnv->hwArcs, &print_hw_dependency_to_file );
        //fprintf(output,"}\n");
        fflush(output);

    } else
        printf("Opening LoopGraph file failed. Please check that folder \"counters\" exists in run directory and has write permission.\n");
   } else {
       printf("Could not open LoopGraph file, please clean \"counters\" folder. (Must contain less than 255 files.)\n");
   }
   
   
   freeListOfArrays(semanticEnv->unitList);
   freeListOfArrays(semanticEnv->commDependenciesList);
   freeListOfArrays(semanticEnv->ctlDependenciesList);
   freeListOfArrays(semanticEnv->dynDependenciesList);
   
   #endif
#ifdef HOLISTIC__TURN_ON_PERF_COUNTERS    
    for(n=0;n<255;n++)
    {
        sprintf(filename, "./counters/Counters.%d.csv",n);
        output = fopen(filename,"r");
        if(output)
        {
            fclose(output);
        }else{
            break;
        }
    }
    if(n<255){
    printf("Saving Counter measurements to File: %s ...\n", filename);
    output = fopen(filename,"w+");
    if(output!=NULL){
        set_counter_file(output);
        int i;
        for(i=0;i<NUM_CORES;i++){
            forAllInListOfArraysDo( semanticEnv->counterList[i], &print_counter_events_to_file );
            fflush(output);
        }

    } else
        printf("Opening UCC file failed. Please check that folder \"counters\" exists in run directory and has write permission.\n");
   } else {
       printf("Could not open UCC file, please clean \"counters\" folder. (Must contain less than 255 files.)\n");
   }
    
#endif
/* It's all allocated inside VMS's big chunk -- that's about to be freed, so
 *  nothing to do here
   

   for( coreIdx = 0; coreIdx < NUM_CORES; coreIdx++ )
    {
      VMS_int__free( semanticEnv->readyVPQs[coreIdx]->startOfData );
      VMS_int__free( semanticEnv->readyVPQs[coreIdx] );
    }
   VMS_int__free( semanticEnv->readyVPQs );
   
   freeHashTable( semanticEnv->commHashTbl );
   VMS_int__free( _VMSMasterEnv->semanticEnv );
 */
   VMS_SS__cleanup_at_end_of_shutdown();
 }


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

/*
 */
  SlaveVP *
VOMP__create_procr_with( TopLevelFnPtr fnPtr,   void *initData,
                        SlaveVP *creatingPr )
 { VOMPSemReq reqData;

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

   VMS_WL__send_create_slaveVP_req( &reqData, creatingPr );

   return creatingPr->dataRetFromReq;
 }

  SlaveVP *
VOMP__create_procr_with_affinity( TopLevelFnPtr fnPtr, void *initData,
                        SlaveVP *creatingPr,  int32  coreToAssignOnto )
 { VOMPSemReq  reqData;

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

   VMS_WL__send_create_slaveVP_req( &reqData, creatingPr );

   return creatingPr->dataRetFromReq;
 }


  void
VOMP__dissipate_procr( SlaveVP *procrToDissipate )
 {
   VMS_WL__send_dissipate_req( procrToDissipate );
 }


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

void *
VOMP__malloc_to( int32 sizeToMalloc, SlaveVP *owningPr )
 { VOMPSemReq reqData;

   reqData.reqType      = malloc_req;
   reqData.sendPr       = owningPr;
   reqData.sizeToMalloc = sizeToMalloc;

   VMS_WL__send_sem_request( &reqData, owningPr );

   return owningPr->dataRetFromReq;
 }


/*Sends request to Master, which does the work of freeing
 */
void
VOMP__free( void *ptrToFree, SlaveVP *owningPr )
 { VOMPSemReq reqData;

   reqData.reqType      = free_req;
   reqData.sendPr       = owningPr;
   reqData.ptrToFree    = ptrToFree;

   VMS_WL__send_sem_request( &reqData, owningPr );
 }


void
VOMP__transfer_ownership_of_from_to( void *data, SlaveVP *oldOwnerSlv,
                                                  SlaveVP *newOwnerPr )
 {
   //TODO: put in the ownership system that automatically frees when no
   // owners of data left -- will need keeper for keeping data around when
   // future created processors might need it but don't exist yet
 }


void
VOMP__add_ownership_by_to( SlaveVP *newOwnerSlv, void *data )
 {

 }


void
VOMP__remove_ownership_by_from( SlaveVP *loserSlv, void *dataLosing )
 {

 }


/*Causes the VOMP system to remove internal ownership, so data won't be
 * freed when VOMP shuts down, and will persist in the external program.
 *
 *Must be called from the processor that currently owns the data.
 *
 *IMPL: Transferring ownership touches two different virtual processor's
 * state -- which means it has to be done carefully -- the VMS rules for
 * semantic layers say that a work-unit is only allowed to touch the
 * virtual processor it is part of, and that only a single work-unit per
 * virtual processor be assigned to a slave at a time.  So, this has to
 * modify the virtual processor that owns the work-unit that called this
 * function, then create a request to have the other processor modified.
 *However, in this case, the TO processor is the outside, and transfers
 * are only allowed to be called by the giver-upper, so can mark caller of
 * this function as no longer owner, and return -- done.
 */
void
VOMP__transfer_ownership_to_outside( void *data )
 {
   //TODO: removeAllOwnersFrom( data );
 }


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

void
VOMP__send_of_type_to( SlaveVP *sendPr, void *msg, const int type,
                        SlaveVP *receivePr)
 { VOMPSemReq  reqData;

   reqData.receivePr = receivePr;
   reqData.sendPr    = sendPr;
   reqData.reqType   = send_type;
   reqData.msgType   = type;
   reqData.msg       = msg;
   reqData.nextReqInHashEntry = NULL;

      //On ownership -- remove inside the send and let ownership sit in limbo
      // as a potential in an entry in the hash table, when this receive msg
      // gets paired to a send, the ownership gets added to the receivePr --
      // the next work-unit in the receivePr's trace will have ownership.
   VMS_WL__send_sem_request( &reqData, sendPr );

      //When come back from suspend, no longer own data reachable from msg
      //TODO: release ownership here
 }

void
VOMP__send_from_to( void *msg, SlaveVP *sendPr, SlaveVP *receivePr )
 { VOMPSemReq  reqData;

      //hash on the receiver, 'cause always know it, but sometimes want to
      // receive from anonymous sender

   reqData.receivePr = receivePr;
   reqData.sendPr    = sendPr;
   reqData.reqType   = send_from_to;
   reqData.msg       = msg;
   reqData.nextReqInHashEntry = NULL;

   VMS_WL__send_sem_request( &reqData, sendPr );
 }


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

void *
VOMP__receive_any_to( SlaveVP *receivePr )
 {

 }

void *
VOMP__receive_type_to( const int type, SlaveVP *receivePr )
 {       DEBUG__printf1(dbgRqstHdlr,"WL: receive type to: %d", receivePr->slaveID);
   VOMPSemReq  reqData;

   reqData.receivePr = receivePr;
   reqData.reqType   = receive_type;
   reqData.msgType   = type;
   reqData.nextReqInHashEntry = NULL;

   VMS_WL__send_sem_request( &reqData, receivePr );
   
   return receivePr->dataRetFromReq;
 }



/*Call this at point receiving virt pr wants in-coming data.
 * 
 *The reason receivePr must call this is that it modifies the receivPr
 * loc structure directly -- and the VMS rules state a virtual processor
 * loc structure can only be modified by itself.
 */
void *
VOMP__receive_from_to( SlaveVP *sendPr, SlaveVP *receivePr )
 {       DEBUG__printf2(dbgRqstHdlr,"WL: receive from %d to: %d", sendPr->slaveID, receivePr->slaveID);
   VOMPSemReq  reqData;

      //hash on the receiver, 'cause always know it, but sometimes want to
      // receive from anonymous sender

   reqData.receivePr = receivePr;
   reqData.sendPr    = sendPr;
   reqData.reqType   = receive_from_to;
   reqData.nextReqInHashEntry = NULL;

   VMS_WL__send_sem_request( &reqData, receivePr );

   return receivePr->dataRetFromReq;
 }


//===========================================================================
//
/*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.
 */

/*asm function declarations*/
void asm_save_ret_to_singleton(VOMPSingleton *singletonPtrAddr);
void asm_write_ret_from_singleton(VOMPSingleton *singletonPtrAddr);

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

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

   VMS_WL__send_sem_request( &reqData, animPr );
   if( animPr->dataRetFromReq ) //will be 0 or addr of label in end singleton
    {
       VOMPSemEnv *semEnv = VMS_int__give_sem_env_for( animPr );
       asm_write_ret_from_singleton(&(semEnv->fnSingletons[ singletonID]));
    }
 }

/*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
VOMP__start_data_singleton( VOMPSingleton **singletonAddr,  SlaveVP *animPr )
 {
   VOMPSemReq  reqData;

   if( *singletonAddr && (*singletonAddr)->hasFinished )
       goto JmpToEndSingleton;
   
   reqData.reqType          = singleton_data_start;
   reqData.singletonPtrAddr = singletonAddr;

   VMS_WL__send_sem_request( &reqData, animPr );
   if( animPr->dataRetFromReq ) //either 0 or end singleton's return addr
    {    //Assembly code changes the return addr on the stack to the one
         // saved into the singleton by the end-singleton-fn
         //The return addr is at 0x4(%%ebp)
        JmpToEndSingleton:
          asm_write_ret_from_singleton(*singletonAddr);
    }
   //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
VOMP__end_fn_singleton( int32 singletonID, SlaveVP *animPr )
 {
   VOMPSemReq  reqData;

      //don't need this addr until after at least one singleton has reached
      // this function
   VOMPSemEnv *semEnv = VMS_int__give_sem_env_for( animPr );
   asm_write_ret_from_singleton(&(semEnv->fnSingletons[ singletonID]));

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

   VMS_WL__send_sem_request( &reqData, animPr );

EndSingletonInstrAddr:
   return;
 }

void
VOMP__end_data_singleton(  VOMPSingleton **singletonPtrAddr, SlaveVP *animPr )
 {
   VOMPSemReq  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.
//   (*(singletonAddr))->endInstrAddr =  &&EndDataSingletonInstrAddr;


   asm_save_ret_to_singleton(*singletonPtrAddr);

   reqData.reqType          = singleton_data_end;
   reqData.singletonPtrAddr = singletonPtrAddr;

   VMS_WL__send_sem_request( &reqData, animPr );
 }

/*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 VP.
 *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
VOMP__animate_short_fn_in_isolation( PtrToAtomicFn ptrToFnToExecInMaster,
                                    void *data, SlaveVP *animPr )
 {
   VOMPSemReq  reqData;

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

   VMS_WL__send_sem_request( &reqData, animPr );
 }


/*This suspends to the master.
 *First, it looks at the VP's data, to see the highest transactionID that VP
 * 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 "VP_currently_executing" field is non-null, then put requesting VP
 * into queue in the struct.  (At some point a holder will request
 * end-transaction, which will take this VP from the queue and resume it.)
 *If NULL, then write requesting into the field and resume.
 */
void
VOMP__start_transaction( int32 transactionID, SlaveVP *animPr )
 {
   VOMPSemReq  reqData;

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

   VMS_WL__send_sem_request( &reqData, animPr );
 }

/*This suspends to the master, then uses transactionID as index into an
 * array of transaction structures.
 *It looks at VP_currently_executing to be sure it's same as requesting VP.
 * 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 VP_currently_executing field to NULL and resumes.
 *If something in, gets it, sets VP_currently_executing to that VP, then
 * resumes both.
 */
void
VOMP__end_transaction( int32 transactionID, SlaveVP *animPr )
 {
   VOMPSemReq  reqData;

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

   VMS_WL__send_sem_request( &reqData, animPr );
 }
