/* collect.c 

   Performs the actual Garbage Collection
 */

#include "prototypes.h"
#include "header.h"
#include "pageHeader.h"
#include "machine.h"

#include <stdlib.h>
#include <assert.h>

#define MAX(X,Y) ((X) > (Y) ? (X) : (Y))

GEN(inline static void scanListOfPages(PageInfo *head, unsigned int unmark));
GEN(static void processQueueOfCObjects(Deque *q));
GEN(static void mergeWithFromSpace(PageInfo *space));
GEN(static void markVolatileObjects(void));

static void traverseGraph(Word *iqStart);
static void pinQueueOfCObjects(Deque *q);

/* Collect performs a complete garbage collection.  It clears the last used page
   and returns in a consistent state.  AllocPtr is not guaranteed to be valid!!

   I am using gcAllocPtr as the end of the implicitQueue. 
   */
void collect(void)
{
  Word *iqStart;
  PageInfo *piPtr;
  unsigned int pages = 0; /* Passed to resetCollectionBoundaries, but only used
			     for generations. */
  GEN(unsigned int currentGen;)
  GEN(Generation *generation;)
  GEN(unsigned int i;)
  GEN(DECL_TIMER(timeScanPages;))

  DECL_TIMER_ALWAYS(timeCollection);
  DECL_TIMER(timeGraphTraversal);
  DECL_TIMER(timeScanRoots);
  DECL_TIMER(timePinChildren);
  DECL_TIMER(freePages);
  DECL_TIMER(timeUnpinPages);
  DECL_TIMER(timeFlipSpaces);

  GEN(DECL_TIMER(timeVolatiles));

  /*   LOG_GCINFO("Before collection."); */

  STATS(gcInfo.nLiveObjects = 0);
  STATS(gcInfo.nLiveWords = 0);
  STATS(gcInfo.nCObjects = 0);
  STATS(gcInfo.nPinnedObjects = 0);
  STATS(gcInfo.nTraversedObjects = 0);
  STATS(gcInfo.nRoots = 0);
  STATS(gcInfo.nFalseRoots = 0);
  STATS(gcInfo.nDeviousRoots = 0);
  STATS(gcInfo.nDuplicates = 0);
  STATS(gcInfo.nWastedSpace = 0);
  STATS(gcInfo.nLargeObjectPages = 0);
  STATS(gcInfo.nPinnedPages = 0);
  
  START_TIMER_ALWAYS(timeCollection);

  gcInfo.nCollections++;

  /* Create the explicitQueue, the pinnedObject queue.  */
  gcInfo.explicitQueue = newDeque();


#ifdef GENERATIONS
  currentGen = 0;
  pages = gcInfo.nPagesAllocated;
  if((gcInfo.nPages - gcInfo.nPagesInUse) < (RATIO * gcInfo.nPages))
    {
     while(pages < (gcInfo.nPagesInUse/2)
	   && currentGen < NUM_GENERATIONS)
       {
	 pages+=gcInfo.generation[currentGen].nPages;
	 currentGen++;
       }
    }
  gcInfo.currentGeneration = currentGen;

  if(currentGen==0) gcInfo.nBasicCollections++;
  else
    {
      gcInfo.generation[currentGen-1].nCollections++;
    }

  for(i=currentGen;i<NUM_GENERATIONS;i++)
    {
      generation = &gcInfo.generation[i];
      if(generation->cObjects!=NULL)
	{
	  processQueueOfCObjects(gcInfo.generation[i].cObjects);
	  resetDeque(gcInfo.generation[i].cObjects);
	}
    }

  for(i=0;i<currentGen;i++)
    {
      generation = &gcInfo.generation[i];
      mergeWithFromSpace(&(generation->space));
      generation->nPages = 0;
      if(generation->cObjects != NULL)
	{
	  pinQueueOfCObjects(generation->cObjects);
	  freeDeque(generation->cObjects);
	  generation->cObjects=NULL;
	}
    }

  /* generation points into the generation we are collecting INTO. */
  if(currentGen == NUM_GENERATIONS)
    generation = &(gcInfo.generation[NUM_GENERATIONS-1]);
  else
    generation = &gcInfo.generation[currentGen];
#endif

  /* Pin all the children of objects in the C-objects list. Free up the C-Object
   queue, and start a new one. */
  assert(gcInfo.cObjects!=NULL);  

  START_TIMER(timePinChildren);
  pinQueueOfCObjects(gcInfo.cObjects);
  freeDeque(gcInfo.cObjects);

#ifdef GENERATIONS
  if(generation->cObjects==NULL) 
    generation->cObjects = newDeque();
  gcInfo.cObjects = generation->cObjects;
#else
  gcInfo.cObjects = newDeque();
#endif

  END_TIMER(timePinChildren);
  LOG_TIMER("Pin Children", timePinChildren);

  START_TIMER(timeScanRoots);
#ifdef PAGE_BLACKLISTING
  if(10*gcInfo.nBlackListedPages > gcInfo.nPages)
    {
      freeBlackList();
    }
#endif

  /* Scan the stack, pinning and traversing all objects found.  Add them to the
     explicitQueue and the pinnedObject queue. */
  scanRoots();
  END_TIMER(timeScanRoots);
  LOG_TIMER("Root Scan",timeScanRoots);

  piPtr = reservePage(PAGE_USER,NULL);
  LIST_INSERT_END(&gcInfo.toSpaceList,piPtr);
  iqStart = PI_TO_PAGE(piPtr);
  gcAllocPtr = iqStart;

#ifdef GENERATIONS
  START_TIMER(timeScanPages);
  PAGE_AGE(piPtr->header);
  gcInfo.pageCounter = 1;
  /* The order of these operations is very sensitive.  Nothing should be moved
     before scanning roots, but scanListOfPages may move something, so it must
     be run after scanning the roots. */
  scanListOfPages(&(generation->space),1);
  for(i=currentGen+1;i<NUM_GENERATIONS;i++)
    {
      scanListOfPages(&gcInfo.generation[i].space,0);
    }
  END_TIMER(timeScanPages);
  LOG_TIMER("Scan Pages", timeScanPages);
#endif

  /* Perform the graph traversal. */
  START_TIMER(timeGraphTraversal);
  traverseGraph(iqStart);
  END_TIMER(timeGraphTraversal);
  LOG_TIMER("Graph Traversal", timeGraphTraversal);

  /* Free all pages in fromSpace that were not pinned. */
  START_TIMER(freePages);
  freeListOfPages(&gcInfo.fromSpaceList);
  END_TIMER(freePages);
  LOG_TIMER("Free Pages", freePages);

  /* Unpin the pinned pages, unpin the traversed object */
  START_TIMER(timeUnpinPages);
  for(piPtr = gcInfo.pinnedPageList.next; piPtr != &gcInfo.pinnedPageList; 
      piPtr = piPtr->next)
    {
      STATS(gcInfo.nPinnedPages++);

      assert(PAGE_IS_PINNED(piPtr->header));
      fixupPage(piPtr);
      FOR_BITMASKS(piPtr->header &= ~PAGE_BITMASK_UPDATED);
      PAGE_UNPIN(piPtr->header);

      GEN(PAGE_UNMARK(piPtr->header));
      GEN(PAGE_AGE(piPtr->header));
      GEN(gcInfo.pageCounter++);
    }  
  LIST_MERGE(&gcInfo.toSpaceList,&gcInfo.pinnedPageList);

  END_TIMER(timeUnpinPages);
  LOG_TIMER("Unpin Pages", timeUnpinPages);


  START_TIMER(timeFlipSpaces);
#ifdef GENERATIONS
  LIST_MERGE(&(generation->space), &gcInfo.toSpaceList);
  generation->nPages +=gcInfo.pageCounter;
  gcInfo.cObjects = newDeque();
#else
  LIST_MERGE(&gcInfo.fromSpaceList, &gcInfo.toSpaceList);
#endif

  END_TIMER(timeFlipSpaces);
  LOG_TIMER("Flip Spaces",timeFlipSpaces);
 
  /* Free the explicitQueue and the pinnedObjects queue. */
  freeDeque(gcInfo.explicitQueue);

  gcInfo.explicitQueue = NULL;

#ifndef GENERATIONS
  resetCollectionBoundaries(pages);
#endif

  /* Mark pages that volatile objects reside on. */
  GEN(START_TIMER(timeVolatiles));
  GEN(markVolatileObjects());
  GEN(END_TIMER(timeVolatiles));
  GEN(LOG_TIMER("Marking Volatiles",timeVolatiles));

  END_TIMER_ALWAYS(timeCollection);
  ADD_ACCUM_ALWAYS(gcInfo.collectorTime,timeCollection);
  LOG_TIMER("Total Collection",timeCollection);
  LOG_GCINFO("After Collection");

  /* Clear variables in gcInfo. */
  gcInfo.nPagesAllocated = 0;
  STATS(gcInfo.nObjectsAllocated = 0);
  STATS(gcInfo.nWordsAllocated = 0);
}

void resetCollectionBoundaries(unsigned int pages)
{
  if(3*gcInfo.nPagesInUse > gcInfo.collectionBoundary)
    {
      gcInfo.collectionBoundary = (3*gcInfo.nPagesInUse);
    } 
}

#ifdef GENERATIONS

static void markVolatileObjects(void)
{
  Word **object;

  while((object = (Word **)dequeue(gcInfo.volatileObjects)) != NULL)
    {
      if(*object>gcInfo.heap && *object<gcInfo.heapEnd)
	{
	  assert((PAGE_TO_PI(*object)->header & PAGE_USER) != 0);
	  GC_PAGE_MARK(*object);
	}
    }

  resetDeque(gcInfo.volatileObjects);
}

inline static void scanListOfPages(PageInfo *head, unsigned int unmark)
{
  PageInfo *pi;

  pi = head->next;

  while(pi!=head)
    {
#ifdef SCAN_ALL_OLD
      scanPage(pi);
#else
      if(PAGE_MARKED(pi->header))
	{
	  scanPage(pi);
	  if(unmark) PAGE_UNMARK(pi->header);
	}
#endif
      pi = pi->next;
    }
}

static void processQueueOfCObjects(Deque *q)
{
  Word *ptr;
  Deque *explicitQueue = gcInfo.explicitQueue;
  if(q == NULL) return;

  while((ptr = (Word *)dequeue(q)) != NULL)
    {
      processCObjectEarly(ptr, OBJECT_HEADER(ptr), explicitQueue);
    }

}
static void mergeWithFromSpace(PageInfo *head)
{
  PageInfo *pi;

  if(LIST_EMPTY(head)) return;

  pi = head->next;
  while(pi!=head)
    {
      PAGE_UNAGE(pi->header);
      PAGE_UNMARK(pi->header);
      pi = pi->next;
    }
  
  LIST_MERGE(&gcInfo.fromSpaceList,head);
  return;
}
#endif

static void pinQueueOfCObjects(Deque *q)
{
  Word *ptr;

  if(q == NULL) return;

  while((ptr = (Word *)dequeue(q)) != NULL)
    {
      pinChildren(ptr);
    }
}

static void traverseGraph(Word *iqStart)
{
 Word *ptr;
 Word *pageEnd;

 Deque *explicitQueue;

 explicitQueue = gcInfo.explicitQueue;

  /* While both the implicitQueue and the explicitQueue are not empty,
     processObjects off either queue arbitrarily. */
 while(1)
   {
     while( (ptr = (Word *)dequeue(explicitQueue))!= NULL)
       {
	 processObject(ptr, explicitQueue);
       }
     while((ptr = iqDequeue(&iqStart))!=NULL)
       {
	 processObject(ptr, explicitQueue);
       }
     
     if(DEQUE_EMPTY(explicitQueue)) break;
   }

  /* Clear the rest of the last page in toSpace. */
  
  pageEnd = GC_PAGE_END(gcAllocPtr);
  ptr = gcAllocPtr;
  for(;ptr<pageEnd;ptr++)
    {
      *ptr = GC_EMPTY;
    }

}
/* EOF: collect.c */

/* (c) Frederick Smith, Greg Morrisett.
 *     October 1998, all rights reserved.
 *
 *
 *              Copyright 1990-1993 Digital Equipment Corporation
 *                         All Rights Reserved
 *
 * Permission to use, copy, and modify this software and its documentation is
 * hereby granted only under the following terms and conditions.  Both the
 * above copyright notice and this permission notice must appear in all copies
 * of the software, derivative works or modified versions, and any portions
 * thereof, and both notices must appear in supporting documentation.
 *
 * Users of this software agree to the terms and conditions set forth herein,
 * and hereby grant back to Digital a non-exclusive, unrestricted, royalty-free
 * right and license under any changes, enhancements or extensions made to the
 * core functions of the software, including but not limited to those affording
 * compatibility with other hardware or software environments, but excluding
 * applications which incorporate this software.  Users further agree to use
 * their best efforts to return to Digital any such changes, enhancements or
 * extensions that they make and inform Digital of noteworthy uses of this
 * software.  Correspondence should be provided to Digital at:
 * 
 *                       Director of Licensing
 *                       Western Research Laboratory
 *                       Digital Equipment Corporation
 *                       250 University Avenue
 *                       Palo Alto, California  94301  
 * 
 * This software may be distributed (but not offered for sale or transferred
 * for compensation) to third parties, provided such third parties agree to
 * abide by the terms and conditions of this notice.  
 * 
 * THE SOFTWARE IS PROVIDED "AS IS" AND DIGITAL EQUIPMENT CORP. DISCLAIMS ALL
 * WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS.   IN NO EVENT SHALL DIGITAL EQUIPMENT
 * CORPORATION BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
 * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
 * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
 * SOFTWARE.
 */
