/*
 * Copyright 2010  OpenSourceCodeStewardshipFoundation
 *
 * Licensed under BSD
 */



#include <windows.h>
#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 )
 { bool8 retCode;
   int slotIdx, numScheduled, numInFirstChunk, filledSlotIdx;
   SchedSlot      *currSlot, **schedSlots, **filledSlots;
   MasterEnv      *masterEnv;
   CASQueueStruc  *workQ;
   void           *jmpPt;
   
   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;

 
   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..
   masterEnv = _VMSMasterEnv;
   while( masterEnv->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
   masterEnv->stillRunning = TRUE;

      //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
   

      //prepare for scheduling
   masterEnv->numFilled = 0;

      //Poll each slot's Done flag -- slot 0 reseved for master, start at 1
   for( slotIdx = 1; 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->requests );
       }
      if( currSlot->needsProcrAssigned )
       {    //give slot a new virt procr
         retCode =
         (*slaveScheduler)( currSlot, masterEnv->semanticEnv );
         
         if( retCode == 1 )
          { int numFilled = masterEnv->numFilled;

            filledSlots[numFilled] = currSlot;
            masterEnv->numFilled += 1;

            currSlot->needsProcrAssigned = FALSE;
          }
         else if( retCode == -1 ) //scheduler plug-in says to shut down VMS
          {
            //shutdown -- make "end Thd" virt-procs whose nextInstrPt is the
            // coreloop's EndCoreLoopPt -- causing a jump to the EndThread
            // and any other shut-down.
          }
       }
    }

      //put some scheduled slaves in, then continuation, then rest
   numInFirstChunk = masterEnv->numFilled / 2;  //tweak this from experiments
   for( filledSlotIdx = 0; filledSlotIdx < numInFirstChunk; filledSlotIdx++)
    {
      writeCASQ( filledSlots[ filledSlotIdx ]->procrAssignedToSlot, workQ );
    }

      //enqueue continuation of this loop
      // note that After this enqueue, continuation might sneak through
   writeCASQ( schedSlots[0]->procrAssignedToSlot, workQ );//master always slot 0
   for( filledSlotIdx = numInFirstChunk;
        filledSlotIdx < numScheduled;
        filledSlotIdx++)
    {
      writeCASQ( filledSlots[ filledSlotIdx ]->procrAssignedToSlot, workQ );
    }

   masterEnv->numFilled = 0;
   
      //Don't want code above to try to look at requests in masterVirtPr,
      // so leave workDone at FALSE, but do want it to schedule into
      // the slot, so set needs procr assigned to TRUE.
   masterPr->schedSlot->needsProcrAssigned = TRUE;

      //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
      // protect stack might need to jmp directly to asm busy-wait to be
      // sure stack not touched
      //TODO: gdb check that busy-wait doesn't touch stack, so this is safe
      //don't need any regs to be valid when come back, so clobber list empty
      //TODO: gdb the jmp -- make sure it jumps through register or mem
   asm volatile("movl %%esp, %0; \
                 movl %%ebp, %1; \
                 movl  $0x0, %2; \
                 jmp     %3      "
   /* outputs */ : "=m" (masterPr->stackPtr), "=m" (masterPr->framePtr),
                   "=m" (masterEnv->stillRunning)
   /* inputs  */ : "r"  (masterPr->coreLoopStartPt)
   /* clobber */ 
                );
 }


