/*! \file blast-out.c
 *
 * COPYRIGHT (c) 2019 The Fellowship of SML/NJ (http://www.smlnj.org)
 * All rights reserved.
 */

#include "ml-osdep.h"
#include "ml-base.h"
#include "ml-values.h"
#include "ml-objects.h"
#include "ml-heap-image.h"
#include "c-globals-tbl.h"
#include "addr-hash.h"
#include "gc.h"
#include "blast-out.h"
#include "heap-output.h"
#include "heap-io.h"

#define BLAST_ERROR	ML_unit

/* local routines */
PVT ml_val_t BlastUnboxed (ml_state_t *msp, ml_val_t obj);
PVT ml_val_t BlastHeap (ml_state_t *msp, ml_val_t obj, blast_res_t *info);
PVT ml_val_t AllocBlastData (ml_state_t *msp, Addr_t sizeB);


/* BlastOut:
 *
 * Linearize an ML object into a vector of bytes; return ML_unit on errors.
 */
ml_val_t BlastOut (ml_state_t *msp, ml_val_t obj)
{
    blast_res_t		res;
    int			gen;
    ml_val_t		blastedObj;

  /* Collect allocation space */
    InvokeGCWithRoots (msp, 0, &obj, NIL(ml_val_t *));

    gen = GetObjGen (obj);

    if (gen == -1) {
      /* unboxed */
	blastedObj = BlastUnboxed (msp, obj);
    }
    else { /* a regular ML object */
      /* do the blast GC */
/* DEBUG  CheckHeap (msp->ml_heap, msp->ml_heap->numGens); */
	res = BlastGC (msp, &obj, gen);

      /* blast out the image */
	blastedObj = BlastHeap (msp, obj, &res);

      /* repair the heap or finish the GC */
	BlastGC_FinishUp (msp, &res);

/* DEBUG CheckHeap (msp->ml_heap, res.maxGen); */
    }

    return blastedObj;

} /* end of BlastOut */


/* BlastUnboxed:
 *
 * Blast out an unboxed value.
 */
PVT ml_val_t BlastUnboxed (ml_state_t *msp, ml_val_t obj)
{
    ml_blast_hdr_t  blastHdr;
    int		    szB = sizeof(ml_image_hdr_t) + sizeof(ml_blast_hdr_t);
    ml_val_t	    blastedObj;
    writer_t	    *wr;

  /* allocate space for the object */
    blastedObj = AllocBlastData (msp, szB);
    wr = WR_OpenMem (PTR_MLtoC(Byte_t, blastedObj), szB);

    HeapIO_WriteImageHeader (wr, BLAST_UNBOXED);

    blastHdr.numArenas		= 0;
    blastHdr.numBOKinds		= 0;
    blastHdr.numBORegions	= 0;
    blastHdr.hasCode		= FALSE;
    blastHdr.rootObj		= obj;

    WR_Write(wr, &blastHdr, sizeof(blastHdr));

    if (WR_Error(wr))
	return ML_unit;
    else {
	WR_Free(wr);
	SEQHDR_ALLOC (msp, blastedObj, DESC_string, blastedObj, szB);
	return blastedObj;
    }

} /* end of BlastUnboxed */


/* BlastHeap:
 *
 * Blast out the heap image.
 */
PVT ml_val_t BlastHeap (ml_state_t *msp, ml_val_t obj, blast_res_t *info)
{
    heap_t		*heap = msp->ml_heap;
    int			maxGen = info->maxGen;
    Addr_t		totArenaSzB[NUM_ARENAS], totSzB;
    struct {
	Addr_t		    base;	/* the base address of the arena in the heap */
	Addr_t		    offset;	/* the relative position in the merged */
					/* arena. */
    }			adjust[MAX_NUM_GENS][NUM_ARENAS];
    heap_arena_hdr_t	*p, *arenaHdrs[NUM_OBJ_KINDS], *arenaHdrsBuf;
    int			arenaHdrSz, i, j, numArenas;
    ml_val_t	        blastedObj;
    writer_t		*wr;

  /* compute the arena offsets in the heap image */
    for (i = 0;  i < NUM_ARENAS; i++)
	totArenaSzB[i] = 0;
  /* the embedded literals go first */
    totArenaSzB[STRING_INDX] = BlastGC_AssignLitAddrs (info, STRING_INDX, 0);
/* DEBUG SayDebug("%d bytes of string literals\n", totArenaSzB[STRING_INDX]); */
    for (i = 0;  i < maxGen;  i++) {
	for (j = 0;  j < NUM_ARENAS;  j++) {
	    arena_t	*ap = heap->gen[i]->arena[j];
	    adjust[i][j].offset = totArenaSzB[j];
	    if (isACTIVE(ap)) {
/* DEBUG SayDebug("[%d][%d] base = %#x, nextw = %#x, %d bytes\n", */
/* DEBUG i, j, ap->tospBase, ap->nextw, (Addr_t)(ap->nextw) - (Addr_t)(ap->tospBase)); */
		totArenaSzB[j] += (Addr_t)(ap->nextw) - (Addr_t)(ap->tospBase);
		adjust[i][j].base = (Addr_t)(ap->tospBase);
	    }
	    else
		adjust[i][j].base = 0;
	}
    }
/* DEBUG for (i = 0;  i < NUM_ARENAS;  i++) SayDebug ("arena %d: %d bytes\n", i+1, totArenaSzB[i]); */
    /** WHAT ABOUT THE BIG OBJECTS??? **/

  /* Compute the total size of the blasted object */
    for (i = 0, numArenas = 0, totSzB = 0;  i < NUM_ARENAS;  i++) {
	if (totArenaSzB[i] > 0) {
	    numArenas++;
	    totSzB += totArenaSzB[i];
	}
    }
    totSzB += (sizeof(ml_image_hdr_t) + sizeof(ml_blast_hdr_t)
		+ (numArenas * sizeof(heap_arena_hdr_t)));
    /** COUNT SPACE FOR BIG OBJECTS **/

  /* include the space for the external symbols */
    totSzB += sizeof(extern_tbl_hdr_t) + ExportTableSz(info->exportTbl);

  /* allocate the heap object for the blasted representation, and initialize
   * the writer.
   */
    blastedObj = AllocBlastData (msp, totSzB);
    wr = WR_OpenMem (PTR_MLtoC(Byte_t, blastedObj), totSzB);

  /* initialize the arena headers */
    arenaHdrSz = numArenas * sizeof(heap_arena_hdr_t);
    arenaHdrsBuf = (heap_arena_hdr_t *) MALLOC (arenaHdrSz);
    for (p = arenaHdrsBuf, i = 0;  i < NUM_ARENAS;  i++) {
	if (totArenaSzB[i] > 0) {
	    p->gen		    = 0;
	    p->objKind		    = i;
	    p->info.o.baseAddr	    = 0;   /* not used */
	    p->info.o.sizeB	    = totArenaSzB[i];
	    p->info.o.roundedSzB    = -1;  /* not used */
	    p->offset		    = -1;  /* not used */
	    arenaHdrs[i]	    = p;
	    p++;
	}
	else
	    arenaHdrs[i] = NIL(heap_arena_hdr_t *);
    }
    /** WHAT ABOUT BIG OBJECTS **/

  /* blast out the image header */
    if (HeapIO_WriteImageHeader (wr, BLAST_IMAGE) == FAILURE) {
	FREE (arenaHdrsBuf);
	return BLAST_ERROR;
    }

  /* blast out the blast header */
    {
	ml_blast_hdr_t	hdr;

	hdr.numArenas = numArenas;
	hdr.numBOKinds = 0; /** FIX THIS **/
	hdr.numBORegions = 0;   /** FIX THIS **/

	if (isEXTERNTAG(obj)) {
	    ASSERT(numArenas == 0);
	    hdr.rootObj = obj;
	}
	else {
	    aid_t	aid = ADDR_TO_PAGEID(BIBOP, obj);

	    if (IS_BIGOBJ_AID(aid)) {
		embobj_info_t	*p = FindEmbObj(info->embobjTbl, obj);

		if ((p == NIL(embobj_info_t *)) || (p->kind == USED_CODE)) {
		    Error ("blasting big objects not implemented\n");
		    FREE (arenaHdrsBuf);
		    return BLAST_ERROR;
		}
		else
		    hdr.rootObj = p->relAddr;
	    }
	    else {
		Addr_t	addr = PTR_MLtoADDR(obj);
		int	gen = EXTRACT_GEN(aid) - 1;
		int	kind = EXTRACT_OBJC(aid) - 1;
		addr -= adjust[gen][kind].base;
		addr += adjust[gen][kind].offset;
		hdr.rootObj = HIO_TAG_PTR(kind, addr);
	    }
	}

	WR_Write(wr, &hdr, sizeof(hdr));
	if (WR_Error(wr)) {
	    FREE (arenaHdrsBuf);
	    return BLAST_ERROR;
	}
    }

  /* blast out the externals table */
    if (HeapIO_WriteExterns(wr, info->exportTbl) == -1) {
	FREE (arenaHdrsBuf);
	return BLAST_ERROR;
    }

  /* blast out the arena headers */
    WR_Write (wr, arenaHdrsBuf, arenaHdrSz);
    if (WR_Error(wr)) {
	FREE (arenaHdrsBuf);
	return BLAST_ERROR;
    }

  /* blast out the heap itself */
    for (i = 0;  i < NUM_ARENAS;  i++) {
	if (i == STRING_INDX) {
	  /* blast out the embedded literals */
	    BlastGC_BlastLits (wr);
	  /* blast out the rest of the strings */
	    for (j = 0;  j < maxGen;  j++) {
		arena_t	*ap = heap->gen[j]->arena[i];
		if (isACTIVE(ap)) {
		    WR_Write(wr, ap->tospBase,
			(Addr_t)(ap->nextw)-(Addr_t)(ap->tospBase));
		}
	    } /* end for */
	}
	else {
	    for (j = 0;  j < maxGen;  j++) {
		arena_t	*ap = heap->gen[j]->arena[i];
		ml_val_t	*p, *top;
		if (isACTIVE(ap)) {
		    for (p = ap->tospBase, top = ap->nextw;  p < top;  p++) {
			ml_val_t	w = *p;
			if (isBOXED(w)) {
			    aid_t		aid = ADDR_TO_PAGEID(BIBOP, w);
			    if (isUNMAPPED(aid)) {
				w = ExportCSymbol(info->exportTbl, w);
				ASSERT (w != ML_unit);
			    }
			    else if (IS_BIGOBJ_AID(aid)) {
				embobj_info_t	*objInfo
						    = FindEmbObj(info->embobjTbl, w);

				if ((objInfo == NIL(embobj_info_t *))
				|| (objInfo->kind == USED_CODE))
				    Die("blast bigobj unimplemented");
				else
				    w = objInfo->relAddr;
			    }
			    else {
			      /* adjust the pointer */
				int		gen = EXTRACT_GEN(aid)-1;
				int		kind = EXTRACT_OBJC(aid)-1;
				Addr_t	addr = PTR_MLtoADDR(w);
				addr -= adjust[gen][kind].base;
				addr += adjust[gen][kind].offset;
				w = HIO_TAG_PTR(kind, addr);
			    }
			}
			WR_Put(wr, (Word_t)w);
		    }
		}
	    } /* end for */
	}
    }

    FREE (arenaHdrsBuf);

    if (WR_Error(wr))
	return BLAST_ERROR;
    else {
	SEQHDR_ALLOC (msp, blastedObj, DESC_string, blastedObj, totSzB);
	return blastedObj;
    }

} /* end of BlastHeap */


/* AllocBlastData:
 *
 * Allocate some heap memory for blasting an object.
 */
PVT ml_val_t AllocBlastData (ml_state_t *msp, Addr_t sizeB)
{
    heap_t	    *heap = msp->ml_heap;
    int		    nWords = BYTES_TO_WORDS(sizeB);
    ml_val_t	    desc = MAKE_DESC(nWords, DTAG_raw);
    ml_val_t	    res;

/** we probably should allocate space in the big-object region for these objects **/
    if (sizeB >= heap->allocSzB-(8*ONE_K)) {
	Die ("blasting out of %d bytes not supported yet!  Increase allocation arena size.",
	    sizeB);
    }

    ML_AllocWrite (msp, 0, desc);
    res = ML_Alloc (msp, nWords);
    return res;

} /* end of AllocBlastData */

