/*! \file flip.c
 *
 * COPYRIGHT (c) 2025 The Fellowship of SML/NJ (http://www.smlnj.org)
 * All rights reserved.
 *
 * This code determines which generations to flip and what the
 * to-space sizes should be.
 *
 * \author John Reppy
 */

#include "ml-base.h"
#include "ml-limits.h"
#include "ml-state.h"
#include "heap.h"
#include "heap-monitor.h"

#if defined(VERBOSE)
extern char	*ArenaName[NUM_ARENAS+1];
#endif


/* Flip:
 *
 * Determine which generations need to be flipped and flip them.  Return
 * the number of flipped generations (which will be at least min_gc_level).
 * It is assumed that the fist generation is always flipped (i.e., that
 * min_gc_level > 1).
 */
int Flip (heap_t *heap, int min_gc_level)
{
    int		i, j, prevGC, numGCs;
    Addr_t	newSz, prevOldSz[NUM_ARENAS], minSize[NUM_ARENAS];
    arena_t	*ap;

#ifdef VERBOSE
SayDebug ("Flip: min_gc_level = %d\n", min_gc_level);
#endif

    /* for the first generation, we make a worst-case assumption that all of the
     * data could be forwarded to any arena.
     */
    for (i = 0;  i < NUM_ARENAS;  i++) {
	prevOldSz[i] = heap->allocSzB;
    }

    prevGC = heap->numMinorGCs;
    for (i = 0;  i < heap->numGens;  i++) {
	gen_t	*g = heap->gen[i];

      /* Check to see if generation (i+1) should be flipped */
#ifdef VERBOSE
SayDebug ("checking generation %d\n", i+1);
#endif
	if (i >= min_gc_level) {
	    for (j = 0;  j < NUM_ARENAS; j++) {
		arena_t	*ap = g->arena[j];
#ifdef VERBOSE
SayDebug ("  %s: avail = %u, prev = %u\n",
ArenaName[j+1], (isACTIVE(ap) ? AVAIL_SPACE(ap) : 0), prevOldSz[j]);
#endif
		if ((isACTIVE(ap) ? AVAIL_SPACE(ap) : 0) < prevOldSz[j])
		    goto flip;
	    }
	  /* Here we don't need to flip gen[i] */
	    return i;
	}
      flip:; /* Here we need to flip gen[i] */

	numGCs = prevGC - g->lastPrevGC;
#ifdef VERBOSE
SayDebug ("Flip generation %d: (%d GCs)\n", i+1, numGCs);
#endif
      /* Compute the space requirements for this generation, make the old
       * to-space into from-space, and allocate a new to-space.
       */
	for (j = 0;  j < NUM_ARENAS;  j++) {
	    Addr_t	minSz, thisMinSz;
	    ap = g->arena[j];
	    if (isACTIVE(ap)) {
		FLIP_ARENA(ap);
		HeapMon_MarkFromSp (heap, ap->frspBase, ap->frspSizeB);
                ASSERT((Addr_t)(ap->oldTop) <= (Addr_t)(ap->frspTop));
		thisMinSz = ((Addr_t)(ap->frspTop) - (Addr_t)(ap->oldTop));
	    }
	    else {
		ap->frspSizeB = 0;  /* to ensure accurate stats */
		if ((ap->reqSizeB == 0) && (prevOldSz[j] == 0)) {
                    /* there will be no data copied into this arena, so skip it */
                    minSize[j] = 0;
		    continue;
                }
		else {
		    thisMinSz = 0;
                }
	    }
	    minSz = prevOldSz[j] + thisMinSz + ap->reqSizeB;
            if (j == STRING_INDX) {
#ifdef SIZE_32
              /* Doubles can require aligment fixups, which in the worst
               * case are 1/3 the size of the object. Round up the minimum
               * size to avoid an overrun on the end of TO space.
               */
                minSz *= 1.33;
#endif
            }
	    else if (j == PAIR_INDX) {
	      /* the first slot isn't used, but may need the space for poly = */
		minSz += 2*WORD_SZB;
            }
	    minSize[j] = minSz;

#ifdef OLD_POLICY
	  /* The desired size is the minimum size times the ratio for the arena,
	   * but it shouldn't exceed the maximum size for the arena (unless
	   * minSz > maxSizeB).
	   */
	    newSz = (ap->ratio * minSz) / RATIO_UNIT;
	    if (newSz < minSz+ap->reqSizeB)
		newSz = minSz+ap->reqSizeB;
#endif
	  /* The desired size is one that will allow "ratio" GCs of the
	   * previous generation before this has to be collected again.
	   * We approximate this as ((f*ratio) / n), where
	   *   f == # of bytes forwarded since the last collection of this generation
	   *   n == # of collections of the previous generation since the last
	   *        collection of this generation
	   * We also need to allow space for young objects in this generation,
	   * but the new size shouldn't exceed the maximum size for the arena
	   * (unless minSz > maxSizeB).
	   */
	    newSz = prevOldSz[j] + ap->reqSizeB + (g->ratio * (thisMinSz / numGCs));
	    if (newSz < minSz) {
		newSz = minSz;
            }
#ifdef VERBOSE
SayDebug ("  %s: min = %u, prev = %u, thisMin = %u, req = %u, new = %u, max = %u\n",
ArenaName[j+1], minSz, prevOldSz[j], thisMinSz, ap->reqSizeB, newSz, ap->maxSizeB);
#endif
	    if (newSz > ap->maxSizeB) {
		newSz = (minSz > ap->maxSizeB) ? minSz : ap->maxSizeB;
            }

	    if (newSz > 0) {
		ap->tospSizeB = RND_MEMOBJ_SZB(newSz);
#ifdef VERBOSE
SayDebug ("    alloc %u\n", ap->tospSizeB);
#endif
	    }
	    else {
		ap->nextw = NIL(ml_val_t *);
                ap->sweep_nextw = NIL(ml_val_t *);
		ap->tospTop = NIL(ml_val_t *);
		ap->tospSizeB = 0;
	    }
	  /* Note: any data between ap->oldTop and ap->nextw is "young", and
	   * should stay in this generation.
	   */
	    if (ap->frspSizeB > 0)
		prevOldSz[j] = (Addr_t)(ap->oldTop) - (Addr_t)(ap->frspBase);
	    else
		prevOldSz[j] = 0;
	}

	g->lastPrevGC = prevGC;
	g->numGCs++;
	prevGC = g->numGCs;
	g->fromObj = g->toObj;
	if (NewGeneration(g) == FAILURE) {
	  /* try to allocate the minimum size */
	    Error ("unable to allocate to-space for generation %d; trying smaller size\n", i+1);
	    for (j = 0;  j < NUM_ARENAS;  j++) {
		g->arena[j]->tospSizeB = RND_MEMOBJ_SZB(minSize[j]);
	    }
	    if (NewGeneration(g) == FAILURE)
		Die("unable to allocate minimum size\n");
	}
#ifdef TOSPACE_ID
	for (j = 0;  j < NUM_ARENAS;  j++) {
	    arena_t	*ap = g->arena[j];
	    if (isACTIVE(ap))
		MarkRegion (BIBOP, ap->tospBase, ap->tospSizeB, TOSPACE_AID(i+1));
	}
#endif

	if (isACTIVE(g->arena[ARRAY_INDX]))
	    NewDirtyVector(g);
    }

    return heap->numGens;

} /* end of Flip */
