/*! \file major-gc.c
 *
 * COPYRIGHT (c) 2019 The Fellowship of SML/NJ (http://www.smlnj.org)
 * All rights reserved.
 *
 * This is the regular garbage collector (for collecting the
 * generations).
 */

#ifdef PAUSE_STATS		/* GC pause statistics are UNIX dependent */
#  include "ml-unixdep.h"
#endif

#include "ml-base.h"
#include "ml-limits.h"
#include "ml-state.h"
#include "ml-values.h"
#include "ml-objects.h"
#include "memory.h"
#include "card-map.h"
#include "heap.h"
#include "tags.h"
#include "copy-loop.h"
#include "heap-monitor.h"
#include "ml-timer.h"
#include "gc-stats.h"

#ifdef BO_REF_STATS
PVT long numBO1, numBO2, numBO3;
#define IFBO_COUNT1(aid)	{if (IS_BIGOBJ_AID(aid)) numBO1++;}
#define BO2_COUNT		(numBO2)++
#define BO3_COUNT		(numBO3)++
#else
#define IFBO_COUNT1(aid)	{}
#define BO2_COUNT		{}
#define BO3_COUNT		{}
#endif

#ifdef COUNT_CARDS
#ifndef BIT_CARDS
PVT unsigned long cardCnt1[MAX_NUM_GENS], cardCnt2[MAX_NUM_GENS];
#define COUNT_CARD1(i)	(cardCnt1[i]++)
#define COUNT_CARD2(i)	(cardCnt2[i]++)
#else
PVT unsigned long cardCnt[MAX_NUM_GENS];
#define COUNT_CARD(i)	(cardCnt[i]++)
#endif
#else
#define COUNT_CARD(i)	{}
#define COUNT_CARD1(i)	{}
#define COUNT_CARD2(i)	{}
#endif


/** DEBUG **/
#ifdef  BO_DEBUG
PVT void ScanMem (Word_t *start, Word_t *stop, int gen, int objKind)
{
    bibop_t	    bibop = BIBOP;
    Word_t	    w;
    aid_t	    aid;
    bigobj_region_t *region;
    bigobj_desc_t   *dp;

    while (start < stop) {
	w = *start;
	if (isBOXED(w)) {
	    Addr_t	indx = BIBOP_ADDR_TO_INDEX(w);
	    aid_t	id = INDEX_TO_PAGEID(bibop,indx);
	    switch (EXTRACT_OBJC(id)) {
	      case OBJC_bigobj:
		while (!BO_IS_HDR(id)) {
		    indx--;
		    id = INDEX_TO_PAGEID(bibop,indx);
		}
		region = (bigobj_region_t *)BIBOP_INDEX_TO_ADDR(indx);
		dp = ADDR_TO_BODESC(region, w);
		if (dp->state == BO_FREE) {
		    SayDebug ("** [%d/%d]: %p --> %p; unexpected free big-object\n",
			gen, objKind, start, w);
		}
		break;
	      case OBJC_record:
	      case OBJC_pair:
	      case OBJC_string:
	      case OBJC_array:
		break;
	      default:
		if (id != AID_UNMAPPED)
		    SayDebug ("** [%d/%d]: %p --> %p; strange object class %d\n",
			gen, objKind, start, w, EXTRACT_OBJC(id));
		break;
	    }
	}
	start++;
    }
}
#endif /** BO_DEBUG **/

/* local routines */
PVT void MajorGC_ScanRoots (
	ml_state_t *msp, heap_t *heap, ml_val_t **roots, int maxCollectedGen);
PVT void MajorGC_SweepToSpace (heap_t *heap, int maxCollectedGen, int maxSweptGen);
PVT bool_t MajorGC_SweepToSpArrays (
	heap_t *heap, int maxGen, arena_t *tosp, card_map_t *cm);
PVT ml_val_t MajorGC_ForwardObj (
	heap_t *heap, aid_t maxAid, ml_val_t obj, aid_t id);
PVT bigobj_desc_t *MajorGC_ForwardBigObj (
	heap_t *heap, int maxGen, ml_val_t obj, aid_t id);
PVT ml_val_t MajorGC_FwdSpecial (
	heap_t *heap, aid_t maxAid, ml_val_t *obj, aid_t id, ml_val_t desc);
PVT void TrimHeap (heap_t *heap, int maxCollectedGen);

/* the symbolic names of the arenas */
char		*ArenaName[NUM_ARENAS+1] = {
	"new", "record", "pair", "string", "array"
    };

/* Check a word for a from-space reference */
#ifdef TOSPACE_ID
#define NO_GC_INLINE /* DEBUG */
#endif
#ifndef NO_GC_INLINE
#define MajorGC_CheckWord(heap,bibop,maxAid,p)	{			\
	ml_val_t	__w = *(p);					\
	if (isBOXED(__w)) {						\
	    aid_t	__aid = ADDR_TO_PAGEID(bibop, __w);		\
IFBO_COUNT1(__aid);							\
	    if (IS_FROM_SPACE(__aid,maxAid)) {				\
		*(p) = MajorGC_ForwardObj(heap, maxAid, __w, __aid);	\
	    }								\
        }								\
    }
#else
PVT void MajorGC_CheckWord (heap_t *heap, bibop_t bibop, aid_t maxAid, ml_val_t *p)
{
    ml_val_t	w = *(p);
    if (isBOXED(w)) {
	aid_t	arena_id = ADDR_TO_PAGEID(bibop, w);
IFBO_COUNT1(arena_id);							\
	if (IS_FROM_SPACE(arena_id, maxAid)) {
	    *(p) = MajorGC_ForwardObj(heap, maxAid, w, arena_id);
	}
#ifdef TOSPACE_ID
	else if (IS_TOSPACE_AID(arena_id)) {
	    Die ("CheckWord: TOSPACE reference: %p (%p) --> %p\n",
		p, ADDR_TO_PAGEID(bibop, p), w);
	}
#endif
    }
}
#endif


/* MajorGC:
 *
 * Do a garbage collection of (at least) the first level generations.
 * By definition, level should be at least 1.
 */
void MajorGC (ml_state_t *msp, ml_val_t **roots, int level)
{
    heap_t	*heap = msp->ml_heap;
    bibop_t	bibop = BIBOP;
    int		i, j;
    int		maxCollectedGen;	/* the oldest generation being collected */
    int		maxSweptGen;
    ml_val_t	*tospTop[NUM_ARENAS]; /* for counting # of bytes forwarded */

#ifndef PAUSE_STATS	/* don't do timing when collecting pause data */
    StartGCTimer(msp->ml_vproc);
#endif
#ifdef BO_REF_STATS
    numBO1 = numBO2 = numBO3 = 0;
#endif

  /* Flip to-space and from-space */
    maxCollectedGen = Flip (heap, level);
    if (maxCollectedGen < heap->numGens) {
	maxSweptGen = maxCollectedGen+1;
      /* Remember the top of to-space for maxSweptGen */
	for (i = 0;  i < NUM_ARENAS;  i++) {
	    tospTop[i] = heap->gen[maxSweptGen-1]->arena[i]->nextw;
        }
    }
    else {
	maxSweptGen = maxCollectedGen;
    }
    NUM_GC_GENS(maxCollectedGen);	/* record pause info */

#ifdef VM_STATS
    ReportVM (msp, maxCollectedGen);
#endif

#ifndef PAUSE_STATS	/* don't do messages when collecting pause data */
    if (GCMessages) {
	SayDebug ("GC #");
	for (i = heap->numGens-1;  i >= 0; i--) {
	    SayDebug ("%d.", heap->gen[i]->numGCs);
	}
	SayDebug ("%d:  ", heap->numMinorGCs);
    }
#endif

    HeapMon_StartGC (heap, maxCollectedGen);

  /* Scan the roots */
    MajorGC_ScanRoots (msp, heap, roots, maxCollectedGen);

  /* Sweep to-space */
    MajorGC_SweepToSpace (heap, maxCollectedGen, maxSweptGen);

  /* Handle weak pointers */
    if (heap->weakList != NIL(ml_val_t *))
	ScanWeakPtrs (heap);

  /* reclaim from-space; we do this from oldest to youngest so that
   * we can promote big objects.
   */
    for (i = maxCollectedGen;  i > 0;  i--) {
	gen_t		*gen = heap->gen[i-1], *promoteGen;
	int		forwardState, promoteState;

	FreeGeneration (heap, i-1);
#ifdef TOSPACE_ID
	for (j = 0;  j < NUM_ARENAS;  j++) {
	    arena_t	*ap = gen->arena[j];
	    if (isACTIVE(ap))
		MarkRegion (bibop, ap->tospBase, ap->tospSizeB, ap->id);
	}
#endif
      /* NOTE: there should never be any big-objects in the oldest generation
       * with the BO_PROMOTE tag.
       */
	if (i != heap->numGens) {
	    promoteGen = heap->gen[i];
	    forwardState = BO_OLD;
	  /* the objects promoted from generation i to generation i+1, when
	   * generation i+1 is also being collected, are "OLD", thus we need
	   * to mark the corresponding big objects as old so that they do not
	   * get out of sync.  Since the oldest generation has only YOUNG
	   * objects, we have to check for that case too.
	   */
	    if ((i == maxCollectedGen) || (i == heap->numGens-1))
		promoteState = BO_YOUNG;
	    else
		promoteState = BO_OLD;
	}
	else {
	  /* oldest generation objects are promoted to the same generation */
	    promoteGen = heap->gen[i-1];
	    forwardState = BO_YOUNG; /* oldest gen has only YOUNG objects */
	    promoteState = BO_YOUNG;
	}
	for (j = 0;  j < NUM_BIGOBJ_KINDS;  j++) {
	    bigobj_desc_t   *dp, *dq, *forward, *promote;
	    promote = promoteGen->bigObjs[j];
	    forward = NIL(bigobj_desc_t *);
	    for (dp = gen->bigObjs[j];  dp != NIL(bigobj_desc_t *);  ) {
		dq = dp->next;
		ASSERT(dp->gen == i);
		switch (dp->state) {
		  case BO_YOUNG:
		  case BO_OLD:
		    BO_Free (heap, dp);
		    break;
		  case BO_FORWARD:
		    dp->state = forwardState;
		    dp->next = forward;
		    forward = dp;
		    break;
		  case BO_PROMOTE:
		    dp->state = promoteState;
		    dp->next = promote;
		    dp->gen++;
		    promote = dp;
		    break;
		  default:
		    Die ("strange bigobject state %d @ %p in generation %d\n",
			dp->state, dp, i);
		} /* end switch */
		dp = dq;
	    }
	    promoteGen->bigObjs[j] = promote; /* a nop for the oldest generation */
	    gen->bigObjs[j] = forward;
	}
    }

#ifdef BO_DEBUG
    /** DEBUG **/
    for (i = 0;  i < heap->numGens;  i++) {
        gen_t	*gen = heap->gen[i];
        ScanMem((Word_t *)(gen->arena[RECORD_INDX]->tospBase), (Word_t *)(gen->arena[RECORD_INDX]->nextw), i+1, RECORD_INDX);
        ScanMem((Word_t *)(gen->arena[PAIR_INDX]->tospBase), (Word_t *)(gen->arena[PAIR_INDX]->nextw), i+1, PAIR_INDX);
        ScanMem((Word_t *)(gen->arena[ARRAY_INDX]->tospBase), (Word_t *)(gen->arena[ARRAY_INDX]->nextw), i+1, ARRAY_INDX);
    }
    /** DEBUG **/
#endif

  /* relabel BIBOP entries for big-object regions to reflect promotions */
    {
	bigobj_region_t	*rp;
	bigobj_desc_t	*dp;
	int		min;

	for (rp = heap->bigRegions;  rp != NIL(bigobj_region_t *);  rp = rp->next) {
	  /* if the minimum generation of the region is less than or equal
	   * to maxCollectedGen, then it is possible that it has increased
	   * as a result of promotions or freeing of objects.
	   */
	    if (rp->minGen <= maxCollectedGen) {
		min = MAX_NUM_GENS;
		for (i = 0;  i < rp->nPages; ) {
		    dp = rp->objMap[i];
		    if ((! BO_IS_FREE(dp)) && (dp->gen < min))
			min = dp->gen;
		    i += BO_NUM_BOPAGES(dp);
		}
		if (rp->minGen != min) {
		    rp->minGen = min;
		    MarkRegion (bibop, (ml_val_t *)rp, MEMOBJ_SZB(rp->memObj),
			AID_BIGOBJ(min));
		    BIBOP_UPDATE(bibop, BIBOP_ADDR_TO_INDEX(rp), AID_BIGOBJ_HDR(min));
		}
	    }
	} /* end for */
    }

  /* remember the top of to-space in the collected generations */
    for (i = 0;  i < maxCollectedGen;  i++) {
	gen_t *g = heap->gen[i];
	if (i == heap->numGens-1) {
	  /* the oldest generation has only "young" objects */
	    for (j = 0;  j < NUM_ARENAS;  j++) {
		if (isACTIVE(g->arena[j]))
		    g->arena[j]->oldTop = g->arena[j]->tospBase;
		else
		    g->arena[j]->oldTop = NIL(ml_val_t *);
	    }
	}
	else {
	    for (j = 0;  j < NUM_ARENAS;  j++) {
		if (isACTIVE(g->arena[j]))
		    g->arena[j]->oldTop = g->arena[j]->nextw;
		else
		    g->arena[j]->oldTop = NIL(ml_val_t *);
	    }
	}
    }

    HeapMon_UpdateHeap (heap, maxSweptGen);

    /* Count the number of forwarded bytes */
    if (maxSweptGen != maxCollectedGen) {
	gen_t *gen = heap->gen[maxSweptGen-1];
	for (j = 0;  j < NUM_ARENAS;  j++) {
	    CNTR_INCR(&(heap->numCopied[maxSweptGen-1][j]),
		gen->arena[j]->nextw - tospTop[j]);
	}
    }
    for (i = 0;  i < maxCollectedGen;  i++) {
	for (j = 0;  j < NUM_ARENAS;  j++) {
	    arena_t	*ap = heap->gen[i]->arena[j];
	    if (isACTIVE(ap)) {
                Addr_t nbytes = (Addr_t)ap->nextw - (Addr_t)ap->tospBase;
		CNTR_INCR(&(heap->numCopied[i][j]), nbytes);
	    }
	}
    }

#ifdef BO_REF_STATS
SayDebug ("bigobj stats: %d seen, %d lookups, %d forwarded\n",
numBO1, numBO2, numBO3);
#endif
#ifndef PAUSE_STATS	/* don't do timing when collecting pause data */
    if (GCMessages) {
	long	gcTime;
	StopGCTimer (msp->ml_vproc, &gcTime);
	SayDebug (" (%d ms)\n", gcTime);
    }
    else
	StopGCTimer (msp->ml_vproc, NIL(long *));
#endif

#ifdef VM_STATS
    ReportVM (msp, 0);
#endif

#ifdef CHECK_HEAP
    CheckHeap(heap, maxSweptGen);
#endif

    if (! UnlimitedHeap)
	TrimHeap (heap, maxCollectedGen);

} /* end of MajorGC. */


/* MajorGC_ScanRoots:
 */
PVT void MajorGC_ScanRoots (
    ml_state_t	*msp,
    heap_t	*heap,
    ml_val_t	**roots,
    int		maxCollectedGen)
{
    bibop_t	bibop = BIBOP;
    aid_t	maxAid = MAKE_MAX_AID(maxCollectedGen);
    ml_val_t	*rp;
    int		i;

    while ((rp = *roots++) != NIL(ml_val_t *)) {
	MajorGC_CheckWord(heap, bibop, maxAid, rp);
    }

  /* Scan the dirty cards in the older generations */
    for (i = maxCollectedGen;  i < heap->numGens;  i++) {
	gen_t	    *gen = heap->gen[i];
#ifdef COUNT_CARDS
#ifndef BIT_CARDS
/*CARD*/cardCnt1[i]=cardCnt2[i]=0;
#else
/*CARD*/cardCnt[i]=0;
#endif
#endif
	if (isACTIVE(gen->arena[ARRAY_INDX])) {
	    card_map_t	*cm = gen->dirty;
	    if (cm != NIL(card_map_t *)) {
		ml_val_t    *maxSweep = gen->arena[ARRAY_INDX]->sweep_nextw;
		int	    card;
#if (!defined(BIT_CARDS) && defined(TOSPACE_ID))
		FOR_DIRTY_CARD (cm, maxCollectedGen, card, {
		    ml_val_t	*p = (cm->baseAddr + (card*CARD_SZW));
		    ml_val_t	*q = p + CARD_SZW;
		    int		mark = i+1;
COUNT_CARD1(i);
		    if (q > maxSweep)
		      /* don't sweep above the allocation high-water mark */
			q = maxSweep;
		    for (;  p < q;  p++) {
			ml_val_t	w = *p;
			if (isBOXED(w)) {
			    aid_t	aid = ADDR_TO_PAGEID(bibop, w);
			    int		targetGen;
IFBO_COUNT1(aid);
			    if (IS_FROM_SPACE(aid, maxAid)) {
			      /* this is a from-space object */
			        if (IS_BIGOBJ_AID(aid)) {
				    bigobj_desc_t	*dp;
				    dp = MajorGC_ForwardBigObj (
					heap, maxCollectedGen, w, aid);
				    targetGen = dp->gen;
			        }
			        else {
				    *p =
				    w = MajorGC_ForwardObj(heap, maxAid, w, aid);
				    aid = ADDR_TO_PAGEID(bibop, w);
				    if (IS_TOSPACE_AID(aid))
				        targetGen = TOSPACE_GEN(aid);
				    else
				        targetGen = EXTRACT_GEN(aid);
			        }
			        if (targetGen < mark)
				    mark = targetGen;
			    }
		        }
		    } /* end of for */
		  /* re-mark the card */
		    ASSERT(cm->map[card] <= mark);
		    if (mark <= i)
			cm->map[card] = mark;
		    else if (i == maxCollectedGen)
			cm->map[card] = CARD_CLEAN;
		});
#elif (!defined(BIT_CARDS))
		FOR_DIRTY_CARD (cm, maxCollectedGen, card, {
		    ml_val_t	*p = (cm->baseAddr + (card*CARD_SZW));
		    ml_val_t	*q = p + CARD_SZW;
		    int		mark = i+1;
COUNT_CARD1(i);
		    if (q > maxSweep)
		      /* don't sweep above the allocation high-water mark */
			q = maxSweep;
		    for (;  p < q;  p++) {
			ml_val_t	w = *p;
			if (isBOXED(w)) {
			    aid_t	aid = ADDR_TO_PAGEID(bibop, w);
			    int		targetGen;
IFBO_COUNT1(aid);
			    if (IS_FROM_SPACE(aid, maxAid)) {
			      /* this is a from-space object */
COUNT_CARD2(i);
			        if (IS_BIGOBJ_AID(aid)) {
				    bigobj_desc_t	*dp;
				    dp = MajorGC_ForwardBigObj (
					heap, maxCollectedGen, w, aid);
				    targetGen = dp->gen;
			        }
			        else {
				    *p =
				    w = MajorGC_ForwardObj(heap, maxAid, w, aid);
				    targetGen = EXTRACT_GEN(ADDR_TO_PAGEID(bibop, w));
			        }
			        if (targetGen < mark)
				    mark = targetGen;
			    }
		        }
		    } /* end of for */
		  /* re-mark the card */
		    ASSERT(cm->map[card] <= mark);
		    if (mark <= i)
			cm->map[card] = mark;
		    else if (i == maxCollectedGen)
			cm->map[card] = CARD_CLEAN;
		});
#else
		FOR_DIRTY_CARD (cm, card, {
		    ml_val_t	*p = (cm->baseAddr + (card*CARD_SZW));
		    ml_val_t	*q = p + CARD_SZW;
COUNT_CARD(i);
		    if (q > maxSweep)
		      /* don't sweep above the allocation high-water mark */
			q = maxSweep;
		    for (;  p < q;  p++) {
			MajorGC_CheckWord (heap, bibop, maxAid, p);
		    }
		});
#endif
	    }
	}
    } /* end of for */

#ifdef COUNT_CARDS
/*CARD*/SayDebug ("\n[%d] SWEEP: ", maxCollectedGen);
/*CARD*/for(i = maxCollectedGen;  i < heap->numGens;  i++) {
/*CARD*/  card_map_t  *cm = heap->gen[i]->dirty;
/*CARD*/  if (i > maxCollectedGen) SayDebug (", ");
#ifndef BIT_CARDS
/*CARD*/  SayDebug ("[%d] %d/%d/%d", i+1, cardCnt1[i], cardCnt2[i],
/*CARD*/	(cm != NIL(card_map_t*)) ? cm->numCards : 0);
#else
/*CARD*/  SayDebug ("[%d] %d/%d", i+1, cardCnt[i],
/*CARD*/	(cm != NIL(card_map_t*)) ? cm->numCards : 0);
#endif
/*CARD*/}
/*CARD*/SayDebug ("\n");
#endif

} /* end of MajorGC_ScanRoots */


/* MajorGC_SweepToSpace:
 * Sweep the to-space arenas.  Because there are few references forward in time, we
 * try to completely scavenge a younger generation before moving on to the
 * next oldest.
 */
PVT void MajorGC_SweepToSpace (heap_t *heap, int maxCollectedGen, int maxSweptGen)
{
    int		i;
    bool_t	swept;
    bibop_t	bibop = BIBOP;
    aid_t	maxAid = MAKE_MAX_AID(maxCollectedGen);

#define SweepToSpArena(gen, indx)	{					\
	arena_t	    *__ap = (gen)->arena[(indx)];				\
	if (isACTIVE(__ap)) {							\
	    ml_val_t    *__p, *__q;						\
	    __p = __ap->sweep_nextw;						\
	    if (__p < __ap->nextw) {						\
		swept = TRUE;							\
		do {								\
		    for (__q = __ap->nextw;  __p < __q;  __p++) {		\
			MajorGC_CheckWord(heap, bibop, maxAid, __p);		\
		    }								\
		} while (__q != __ap->nextw);					\
		__ap->sweep_nextw = __q;					\
	    }									\
	}									\
    } /* SweepToSpArena */

    do {
	swept = FALSE;
	for (i = 0;  i < maxSweptGen;  i++) {
	    gen_t	*gen = heap->gen[i];

	  /* Sweep the record and pair arenas */
	    SweepToSpArena(gen, RECORD_INDX);
	    SweepToSpArena(gen, PAIR_INDX);

	  /* Sweep the array arena */
	    {
		arena_t		*ap = gen->arena[ARRAY_INDX];
		if (isACTIVE(ap)
		&& MajorGC_SweepToSpArrays (heap, maxCollectedGen, ap, gen->dirty))
		    swept = TRUE;
	    }
	}
    } while (swept);

}/* end of SweepToSpace */


/* MajorGC_SweepToSpArrays:
 *
 * Sweep the to-space of the array arena, returning true if any objects
 * are actually swept.
 */
PVT bool_t MajorGC_SweepToSpArrays (
	heap_t *heap, int maxGen, arena_t *tosp, card_map_t *cm)
{
    ml_val_t	w, *p, *stop;
    int		thisGen;
    Word_t	cardMask = ~(CARD_SZB - 1);
    bibop_t	bibop = BIBOP;
    aid_t	maxAid = MAKE_MAX_AID(maxGen);
#ifndef BIT_CARDS
    ml_val_t	*cardStart;
    int		cardMark;
#endif

  /* Sweep a single card at a time, looking for references that need to
   * be remembered.
   */
    thisGen = EXTRACT_GEN(tosp->id);
    p = tosp->sweep_nextw;
    if (p == tosp->nextw)
	return FALSE;
    while (p < tosp->nextw) {
	stop = (ml_val_t *)(((Addr_t)p + CARD_SZB) & cardMask);
	if (stop > tosp->nextw)
	    stop = tosp->nextw;
      /* Sweep the next page until we see a reference to a younger generation */
#ifndef BIT_CARDS
	cardStart = p;
	cardMark = CARD(cm, cardStart);
#endif
	while (p < stop) {
	    if (isBOXED(w = *p)) {
		aid_t		arena_id = ADDR_TO_PAGEID(bibop, w);
		int		targetGen;

IFBO_COUNT1(arena_id);
		if (IS_FROM_SPACE(arena_id, maxAid)) {
		  /* this is a from-space object */
		    if (IS_BIGOBJ_AID(arena_id)) {
			bigobj_desc_t	*dp;
			dp = MajorGC_ForwardBigObj (heap, maxGen, w, arena_id);
			targetGen = dp->gen;
		    }
		    else {
			*p = w = MajorGC_ForwardObj(heap, maxAid, w, arena_id);
#ifdef TOSPACE_ID
			{ aid_t aid = ADDR_TO_PAGEID(bibop, w);
			  if (IS_TOSPACE_AID(aid))
			    targetGen = TOSPACE_GEN(aid);
			  else
			    targetGen = EXTRACT_GEN(aid);
			}
#else
			targetGen = EXTRACT_GEN(ADDR_TO_PAGEID(bibop, w));
#endif
		    }
#ifndef BIT_CARDS
		    if (targetGen < cardMark)
			cardMark = targetGen;
#else
		    if (targetGen < thisGen) {
		      /* the forwarded object is in a younger generation */
			MARK_CARD(cm, p);
		      /* finish the card up quickly */
			for (p++; p < stop;  p++) {
			    MajorGC_CheckWord(heap, bibop, maxAid, p);
			}
			break;
		    }
#endif
		}
#ifdef TOSPACE_ID
		else if (IS_TOSPACE_AID(arena_id)) {
		    Die ("Sweep Arrays: TOSPACE reference: %p (%p) --> %p\n",
			p, ADDR_TO_PAGEID(bibop, p), w);
		}
#endif
	    }
	    p++;
	} /* end of while */
#ifndef BIT_CARDS
	if (cardMark < thisGen)
	    MARK_CARD(cm, cardStart, cardMark);
#endif
    } /* end of while */
    tosp->sweep_nextw = p;

    return TRUE;

} /* end of MajorGC_SweepToSpArrays */


/* MajorGC_ForwardObj:
 *
 * Forward an object.
 */
PVT ml_val_t MajorGC_ForwardObj (heap_t *heap, aid_t maxAid, ml_val_t v, aid_t id)
{
    ml_val_t	*obj = PTR_MLtoC(ml_val_t, v);
    ml_val_t	*new_obj;
    ml_val_t	desc;
    Word_t	len;
    arena_t	*arena;

    switch (EXTRACT_OBJC(id)) {
      case OBJC_record: {
	desc = obj[-1];
	switch (GET_TAG(desc)) {
	  case DTAG_vec_hdr:
	  case DTAG_arr_hdr:
	    len = 2;
	    break;
	  case DTAG_forward:
	  /* This object has already been forwarded */
	    return PTR_CtoML(FOLLOW_FWDOBJ(obj));
	  case DTAG_record:
	    len = GET_LEN(desc);
	    break;
	  default:
	    Die ("bad record tag %d, obj = %p, desc = %p",
		GET_TAG(desc), obj, desc);
	} /* end of switch */
	arena = heap->gen[EXTRACT_GEN(id)-1]->arena[RECORD_INDX];
	if (isOLDER(arena, obj))
	    arena = arena->nextGen;
      } break;

      case OBJC_pair: {
	ml_val_t	w;

	w = obj[0];
	if (isDESC(w))
	    return PTR_CtoML(FOLLOW_FWDPAIR(w, obj));
	else {
	  /* forward the pair */
	    arena = heap->gen[EXTRACT_GEN(id)-1]->arena[PAIR_INDX];
	    if (isOLDER(arena, obj))
		arena = arena->nextGen;
	    new_obj = arena->nextw;
	    arena->nextw += PAIR_SZW;
	    new_obj[0] = w;
	    new_obj[1] = obj[1];
	  /* setup the forward pointer in the old pair */
	    obj[0] =  MAKE_PAIR_FP(new_obj);
	    return PTR_CtoML(new_obj);
	}
      } break;

      case OBJC_string: {
	arena = heap->gen[EXTRACT_GEN(id)-1]->arena[STRING_INDX];
	if (isOLDER(arena, obj))
	    arena = arena->nextGen;
	desc = obj[-1];
	switch (GET_TAG(desc)) {
	  case DTAG_forward:
	    return PTR_CtoML(FOLLOW_FWDOBJ(obj));
	  case DTAG_raw:
	    len = GET_LEN(desc);
	    break;
	  case DTAG_raw64:
	    len = GET_LEN(desc);
#ifdef ALIGN_REALDS
#  ifdef CHECK_HEAP
	    if (((Addr_t)arena->nextw & WORD_SZB) == 0) {
		*(arena->nextw) = (ml_val_t)0;
		arena->nextw++;
	    }
#  else
	    arena->nextw = (ml_val_t *)(((Addr_t)arena->nextw) | WORD_SZB);
#  endif
#endif
	    break;
	  default:
	    Die ("bad string tag %d, obj = %p, desc = %p",
		GET_TAG(desc), obj, desc);
	} /* end of switch */
      } break;

      case OBJC_array: {
	desc = obj[-1];
	switch (GET_TAG(desc)) {
	  case DTAG_forward:
	  /* This object has already been forwarded */
	    return PTR_CtoML(FOLLOW_FWDOBJ(obj));
	  case DTAG_arr_data:
	    len = GET_LEN(desc);
	    break;
	  case DTAG_special:
	    return MajorGC_FwdSpecial (heap, maxAid, obj, id, desc);
	  default:
	    Die ("bad array tag %d, obj = %p, desc = %p",
		GET_TAG(desc), obj, desc);
	} /* end of switch */
	arena = heap->gen[EXTRACT_GEN(id)-1]->arena[ARRAY_INDX];
	if (isOLDER(arena, obj))
	    arena = arena->nextGen;
      } break;

      case OBJC_bigobj:
	MajorGC_ForwardBigObj (heap, EXTRACT_GEN(maxAid), v, id);
	return v;

      default:
	Die("unknown object class %d @ %p", EXTRACT_OBJC(id), obj);
    } /* end of switch */

  /* Allocate and initialize a to-space copy of the object */
    new_obj = arena->nextw;
    arena->nextw += (len + 1);
    *new_obj++ = desc;
    ASSERT(arena->nextw <= arena->tospTop);
    COPYLOOP(obj, new_obj, len);

  /* set up the forward pointer, and return the new object. */
    obj[-1] = DESC_forwarded;
    obj[0] = (ml_val_t)(Addr_t)new_obj;

    return PTR_CtoML(new_obj);

} /* end of MajorGC_ForwardObj */


/* MajorGC_ForwardBigObj:
 *
 * Forward a big-object obj, where id is the BIBOP entry for obj.
 * Return the descriptor for obj.
 */
PVT bigobj_desc_t *MajorGC_ForwardBigObj (
	heap_t *heap, int maxGen, ml_val_t obj, aid_t id)
{
    Addr_t	    i;
    bigobj_region_t *region;
    bigobj_desc_t   *dp;

BO2_COUNT;
    i = BIBOP_ADDR_TO_INDEX(obj);
    while (!BO_IS_HDR(id)) {
	--i;
	id = INDEX_TO_PAGEID(BIBOP,i);
    }
    region = (bigobj_region_t *)BIBOP_INDEX_TO_ADDR(i);
    dp = ADDR_TO_BODESC(region, obj);
    if ((dp->gen <= maxGen) && BO_IS_FROM_SPACE(dp)) {
BO3_COUNT;
      /* forward the big-object; note that objects in the oldest generation
       * will always be YOUNG, thus will never be promoted.
       */
	if (dp->state == BO_YOUNG)
	    dp->state = BO_FORWARD;
	else
	    dp->state = BO_PROMOTE;
    }

    return dp;

} /* end of MajorGC_ForwardBigObj */


/* MajorGC_FwdSpecial:
 *
 * Forward a special object (suspension, weak pointer, ...).
 */
PVT ml_val_t MajorGC_FwdSpecial (
    heap_t	*heap,
    aid_t	maxAid,
    ml_val_t	*obj,
    aid_t	id,
    ml_val_t	desc
)
{
    gen_t	*gen = heap->gen[EXTRACT_GEN(id)-1];
    arena_t	*arena = gen->arena[ARRAY_INDX];
    ml_val_t	*new_obj;

    if (isOLDER(arena, obj))
	arena = arena->nextGen;

  /* allocate the new object */
    new_obj = arena->nextw;
    arena->nextw += SPECIAL_SZW;  /* all specials are two words */

    switch (GET_LEN(desc)) {
      case SPCL_evaled_susp:
      case SPCL_unevaled_susp:
      case SPCL_null_weak:
	*new_obj++ = desc;
	*new_obj = *obj;
	break;
      case SPCL_weak: {
	    ml_val_t	v = *obj;
#ifdef DEBUG_WEAK_PTRS
SayDebug ("MajorGC: weak [%p ==> %p] --> %p", obj, new_obj+1, v);
#endif
	    if (! isBOXED(v)) {
#ifdef DEBUG_WEAK_PTRS
SayDebug (" unboxed\n");
#endif
	      /* weak references to unboxed objects are never nullified */
		*new_obj++ = DESC_weak;
		*new_obj = v;
	    }
	    else {
		aid_t		aid = ADDR_TO_PAGEID(BIBOP, v);
		ml_val_t	*vp = PTR_MLtoC(ml_val_t, v);
		ml_val_t	desc;

		if (IS_FROM_SPACE(aid, maxAid)) {
		    switch (EXTRACT_OBJC(aid)) {
		      case OBJC_record:
		      case OBJC_string:
		      case OBJC_array:
			desc = vp[-1];
			if (desc == DESC_forwarded) {
			  /* Reference to an object that has already been forwarded.
			   * NOTE: we have to put the pointer to the non-forwarded
			   * copy of the object (i.e, v) into the to-space copy
			   * of the weak pointer, since the GC has the invariant
			   * it never sees to-space pointers during sweeping.
			   */
#ifdef DEBUG_WEAK_PTRS
SayDebug (" already forwarded to %p\n", FOLLOW_FWDOBJ(vp));
#endif
			    *new_obj++ = DESC_weak;
			    *new_obj = v;
			}
			else {
			  /* the forwarded version of weak objects are threaded
			   * via their descriptor fields.  We mark the object
			   * reference field to make it look like an unboxed value,
			   * so that the to-space sweeper does not follow the weak
			   * reference.
			   */
#ifdef DEBUG_WEAK_PTRS
SayDebug (" forward (start = %p)\n", vp);
#endif
			    *new_obj = MARK_PTR(PTR_CtoML(gen->heap->weakList));
			    gen->heap->weakList = new_obj++;
			    *new_obj = MARK_PTR(vp);
			}
			break;
		      case OBJC_pair:
			if (isDESC(desc = vp[0])) {
			  /* Reference to a pair that has already been forwarded.
			   * NOTE: we have to put the pointer to the non-forwarded
			   * copy of the pair (i.e, v) into the to-space copy
			   * of the weak pointer, since the GC has the invariant
			   * it never sees to-space pointers during sweeping.
			   */
#ifdef DEBUG_WEAK_PTRS
SayDebug (" (pair) already forwarded to %p\n", FOLLOW_FWDPAIR(desc, vp));
#endif
			    *new_obj++ = DESC_weak;
			    *new_obj = v;
			}
			else {
			    *new_obj = MARK_PTR(PTR_CtoML(gen->heap->weakList));
			    gen->heap->weakList = new_obj++;
			    *new_obj = MARK_PTR(vp);
			}
			break;
		      case OBJC_bigobj:
			Die ("weak big object");
			break;
		    }
		}
		else {
		  /* reference to an older object */
#ifdef DEBUG_WEAK_PTRS
SayDebug (" old object\n");
#endif
		    *new_obj++ = DESC_weak;
		    *new_obj = v;
		}
	    }
	} break;
      default:
	Die ("strange/unexpected special object @ %p; desc = %p\n", obj, desc);
    } /* end of switch */

    obj[-1] = DESC_forwarded;
    obj[0] = (ml_val_t)(Addr_t)new_obj;

    return PTR_CtoML(new_obj);

} /* end of MajorGC_FwdSpecial */


/* TrimHeap:
 *
 * After a major collection, trim any arenas that are over their maximum
 * size in allocated space, but under their maximum size in used space.
 */
PVT void TrimHeap (heap_t *heap, int maxCollectedGen)
{
    int			i, j;
    gen_t		*gen;
    arena_t		*ap;
    Word_t		minSzB, newSzB;

    for (i = 0;  i < maxCollectedGen;  i++) {
	gen = heap->gen[i];
	for (j = 0;  j < NUM_ARENAS;  j++) {
	    ap = gen->arena[j];
	    if (isACTIVE(ap) && (ap->tospSizeB > ap->maxSizeB)) {
		minSzB = (i == 0)
		    ? heap->allocSzB
		    : heap->gen[i-1]->arena[j]->tospSizeB;
		minSzB += (USED_SPACE(ap) + ap->reqSizeB);
		if (minSzB < ap->maxSizeB)
		    newSzB = ap->maxSizeB;
		else {
		    newSzB = RND_MEMOBJ_SZB(minSzB);
		  /* the calculation of minSz here may return something bigger
		   * that what flip.c computed!
		   */
		    if (newSzB > ap->tospSizeB)
			newSzB = ap->tospSizeB;
		}
		ap->tospSizeB = newSzB;
		ap->tospTop = (ml_val_t *)((Addr_t)ap->tospBase + ap->tospSizeB);
	    }
	}
    }

} /* end of TrimHeap */
