/*
 * Copyright 2010  OpenSourceCodeStewardshipFoundation
 *
 * Licensed under BSD
 */



#include <stdio.h>
#include <malloc.h>
#include <stddef.h>

#include "VMS.h"



/*This code is animated by the virtual Master processor.
 *
 *Polls each sched slot exactly once, hands any requests made by a newly
 * done slave to the "request handler" plug-in function
 *
 *Any slots that need a virt procr assigned are given to the "schedule"
 * plug-in function, which tries to assign a virt procr (slave) to it.
 *
 *When all slots needing a processor have been given to the schedule plug-in,
 * a fraction of the procrs successfully scheduled are put into the
 * work queue, then a continuation of this function is put in, then the rest
 * of the virt procrs that were successfully scheduled.
 *
 *The first thing the continuation does is busy-wait until the previous
 * animation completes.  This is because an (unlikely) continuation may
 * sneak through queue before previous continuation is done putting second
 * part of scheduled slaves in, which is the only race condition.
 *
 */

/*May 29, 2010 -- birth a Master during init so that first core loop to
 * start running gets it and does all the stuff for a newly born --
 * from then on, will be doing continuation, but do suspension self
 * directly at end of master loop
 *So VMS__init just births the master virtual processor same way it births
 * all the others -- then does any extra setup needed and puts it into the
 * work queue.
 *However means have to make masterEnv a global static volatile the same way
 * did with workQ in core loop.  -- for performance, put the
 * jump to the core loop directly in here, and have it directly jump back.
 */
void masterLoop( void *initData, VirtProcr *masterPr )
 { 
   int             slotIdx, numFilled, filledSlotIdx, masterHasBeenQueued;
   VirtProcr      *schedVirtPr;
   SchedSlot      *currSlot, **schedSlots, **filledSlots;
   MasterEnv      *masterEnv;
   VMSQueueStruc  *workQ;
   void           *jmpPt, *stackPtrAddr, *framePtrAddr, *stillRunningAddr;
   void           *coreLoopFramePtr, *coreLoopStackPtr, *semanticEnv;
   
   SlaveScheduler  slaveScheduler;
   RequestHandler  requestHandler;

      //this will run as the first virt processor in workQ, and will be a
      // new born -- so will do all the GCC-generated allocating space on
      // the stack owned by master virt procr -- and will run this last bit
      // of setup code..
   masterPr->nextInstrPt = &&masterLoopStartPt;

      //The second time MasterVP comes out of queue, the first animation of
      // it hasn't written the stackPtr and framePtr yet -- but the second
      // animation has already had its stackPtr and framePtr set to the old
      // value by the coreLoop.  Fix this by writing the correct stack and
      // frame pointers here, at which point they're correct in the first
      // animation of MasterVP.
      //TODO: remove writing stackPtr and framePtr at the bottom, for eff
   stackPtrAddr      = &(masterPr->stackPtr);
   framePtrAddr      = &(masterPr->framePtr);

   asm volatile("movl %0,     %%eax;  \
                 movl %%esp, (%%eax); \
                 movl %1,     %%eax;  \
                 movl %%ebp, (%%eax); "
   /* outputs */ : "=g" (stackPtrAddr), "=g" (framePtrAddr)                 \
   /* inputs  */ :                                                          \
   /* clobber */ : "memory", "%eax", "%ebx"                                 \
                );


   masterLoopStartPt:

      //if another reference to same Master VirtProcr still going, busy-wait
      //Could put this lower, but don't want to think about shared stack..
   while( _VMSMasterEnv->stillRunning ) /*busy wait*/ ;
      //TODO: want to do busy-wait as assembly, to be sure stack not touched?
   
      //this is the only master running now, set flag again
   _VMSMasterEnv->stillRunning = TRUE;
   masterEnv = _VMSMasterEnv;

      //TODO: gdb -- check that a volatile _VMSMasterEnv and _VMSWorkQ means
      // all these will be re-filled every time jump here..
   workQ            = _VMSWorkQ;
   requestHandler   = masterEnv->requestHandler;
   slaveScheduler   = masterEnv->slaveScheduler;
   schedSlots       = masterEnv->schedSlots;
   filledSlots      = masterEnv->filledSlots;
   masterPr         = masterEnv->masterVirtPr;  //post-jmp clobbered, re-load
   semanticEnv      = masterEnv->semanticEnv;

      //prepare for scheduling
   numFilled = 0;
   masterHasBeenQueued = FALSE;

      //Poll each slot's Done flag -- slot 0 reserved for master, start at 1
   for( slotIdx = 0; slotIdx < NUM_SCHED_SLOTS; slotIdx++)
    {
      currSlot = schedSlots[ slotIdx ];

      if( currSlot->workIsDone )
       {
         currSlot->workIsDone         = FALSE;
         currSlot->needsProcrAssigned = TRUE;

            //process requests from slave to master
         (*requestHandler)( currSlot->procrAssignedToSlot, semanticEnv );
       }
      if( currSlot->needsProcrAssigned )
       {    //give slot a new virt procr
         schedVirtPr =
          (*slaveScheduler)( semanticEnv );
         
         if( schedVirtPr != NULL )
          { currSlot->procrAssignedToSlot = schedVirtPr;
            schedVirtPr->schedSlot        = currSlot;
            currSlot->needsProcrAssigned  = FALSE;

            filledSlots[ numFilled ]      = currSlot;

            writeVMSQ( schedVirtPr, workQ );
            numFilled += 1;
            
            if( numFilled == masterEnv->numToPrecede )
             {
               writeVMSQ( masterEnv->masterVirtPr, workQ );
               masterHasBeenQueued = TRUE;
             }

          }
       }
    }

   if( !masterHasBeenQueued )
    {
      writeVMSQ( masterEnv->masterVirtPr, workQ );
    }

      //Adjust the number to precede, for next round -- assume rate of
      // finishing work is stable -- which is a bad assumption!  But, just
      // want something working for the moment, look at dynamic behavior
      // later
//TODO: look at dynamic behavior -- time-average numToPrecede or something
   if( numFilled < NUM_CORES - 1 )
    { 
      masterEnv->numToPrecede = 1;
    }
   else
    { masterEnv->numToPrecede = numFilled - NUM_CORES + 1;
    }

      //Save stack ptr and frame -- don't need to, take out later, but safe
      // Also, wait to set stillRunning to FALSE until just before jump, to
      // be safe -- although the two simulatneously animated MasterLoops
      // are on different cores, so have different stacks, so no worries
      // there.
      //Restore CoreLoop's stack frame (and stack pointer, to be safe)
      //TODO: cafefully verify don't need to force saving anything to stack
      // before jumping back to core loop.
   stackPtrAddr      = &(masterPr->stackPtr);
   framePtrAddr      = &(masterPr->framePtr);
   stillRunningAddr  = &(_VMSMasterEnv->stillRunning); //when race condition
      //arises, stillRunning is shared between the two cores both animating
      // MasterLoop -- but those two cores have different esp & ebp, so safe
      // to change stack and frame pointer here, without one messing up other
      // one

   jmpPt             = masterPr->coreLoopStartPt;
   coreLoopFramePtr  = masterPr->coreLoopFramePtr;//need this only
   coreLoopStackPtr  = masterPr->coreLoopStackPtr;//shouldn't need -- safety
   
   asm volatile("movl %0,     %%eax;  \
                 movl %%esp, (%%eax); \
                 movl %1,     %%eax;  \
                 movl %%ebp, (%%eax); \
                 movl %2, %%ebx;      \
                 movl %3, %%eax;      \
                 movl %4, %%esp;      \
                 movl %5, %%ebp;      \
                 movl $0x0, (%%ebx);  \
                 jmp  %%eax "         \
   /* outputs */ : "=g" (stackPtrAddr), "=g" (framePtrAddr),                \
                   "=g"(stillRunningAddr)                                   \
   /* inputs  */ : "g" (jmpPt), "g"(coreLoopStackPtr), "g"(coreLoopFramePtr)\
   /* clobber */ : "memory", "%eax", "%ebx", "%ecx", "%edx", "%edi", "%esi" \
                );//can probably make clobber list empty -- but safe for now
 }


