/* $Id$
rankfmt.c
   See comments in header file for usage.
*/

#include "rankfmt.h"
#include <string.h>


/* some options on the format are controlled by global
   variables. Of course for efficiency we'd do it in the
   pre-processor, but that would require recompilation...
*/

int formatcomments=0;
int checkcomments=0;
int countcomments=1;
int alloccomments=0;
int fixcomments=0;
int deathcomments=0;
int skipcomments=0;
int splurgeassoc=0; /* write madly to associated objects */


long int nextid=0x1000000;

long int checkobjcount=0;

mycell *exfmt_root = NULL;

int counters[PAD_COUNT+1] = {0};
int prevcounters[PAD_COUNT+1] = {0};
int maxcounters[PAD_COUNT+1] = {0};

long int maxcopy = 0;
int freeze=0;


/* The AWL pool makes certain assumptions about the object format,
   some necessary and some unnecessary but useful for detecting problems
   when using fmtdy. We have to ensure that:

   Each objects begins with a pointer to a wrapper. To tag objects,
   use non-zero values for the low bits.

   The second word in each object is a pointer to the associated
   object, usual rules for references apply. The getassociated()
   function looks up this reference and can be used as the
   find_dependent function when creating an AWL pool.

   The wrapper begins with a wrapper-wrapper pointer, and the third word
   of the wrapper object has bit 0 set and bit 1 clear and at least one higher
   bit also set. The fourth word is the same, to cater for the new Dylan
   object format used by honeybee.3 and later releases.

   The wrapper-wrapper's first word points to itself.

   Here I make the wrapper-wrapper equal to the wrapper for all objects,
   and use the same wrapper for all objects.

   The other pools don't care about the wrapper, of course.
*/

struct wrapper {
 struct wrapper *ww;
 mps_word_t tag;
 mps_word_t fixedlen;
 mps_word_t newfixedlen;
};

struct wrapper wrapobj = {
 &wrapobj,
 0x36ABBE6,
 0x5,
 0x5
};

mycell *wrapper = (mycell *) &wrapobj;


#define INCCOUNT(c) do {if(!freeze) counters[c]+=1;} while (0)
#define INCCOUNTIF(f, c) do {if(f) INCCOUNT(c);} while (0)


/* a cell can be one of four things, depending on its type:
  MCpad       - a pad item
  MCheart     - a broken heart, aka forwarding object
  MCdata      - a real object
*/


/* the scanning function doesn't try to fix null refs
*/

static mps_res_t myscan(mps_ss_t ss, mps_addr_t base, mps_addr_t limit);
static mps_addr_t myskip(mps_addr_t object);
static void myfwd(mps_addr_t object, mps_addr_t to);
static mps_addr_t myisfwd(mps_addr_t object);
static void mycopy(mps_addr_t object, mps_addr_t to);
static void mypad(mps_addr_t base, size_t size);


struct mps_fmt_A_s fmtA =
{
 MPS_PF_ALIGN,
 &myscan,
 &myskip,
 &mycopy,
 &myfwd,
 &myisfwd,
 &mypad
};


/* in the following, size is the number of refs you want
   the allocated object to have
*/

mps_res_t allocrdumb(mycell **rvar, mps_ap_t ap,
                     size_t size, mps_rank_t rank) {
 mps_addr_t addr;
 mycell *q;
 size_t bytes;
 mps_res_t res;
 size_t alignment;

 bytes = offsetof(struct data, ref) + size;

 alignment = MPS_PF_ALIGN; /* needed to make it as wide as size_t */

 /* twiddle the value of size to make it aligned */
 bytes = (bytes+alignment-1) & ~(alignment-1);

 do
 {
  res = mps_reserve(&addr, ap, bytes);
  if (res) goto Finish;
  INCCOUNT(RESERVE_COUNT);
  q = (mycell *)addr;
  q->data.tag = (mps_word_t) wrapper;
  q->data.assoc = NULL;
  q->data.id = nextid;
  q->data.copycount = 0;
  q->data.numrefs = 0;
  q->data.checkedflag = 0;
  q->data.rank = rank;
  q->data.size = bytes;
 }
 while (!mps_commit(ap, addr, bytes));
 INCCOUNT(ALLOC_COUNT);
 commentif(alloccomments, "allocated id %li at %p.", nextid, q);
 nextid += 1;
 *rvar = q;

Finish:
 return res;
}


mycell *allocdumb(mps_ap_t ap, size_t size, mps_rank_t rank) {
 mycell *q;

 die(allocrdumb(&exfmt_root, ap, size, rank), "Reserve: ");
 q = exfmt_root;
 return q;
}


mps_res_t allocrone(mycell **rvar, mps_ap_t ap, int size, mps_rank_t rank) {
 mps_addr_t addr;
 mycell *q;
 int i;
 size_t bytes;
 size_t alignment;
 mps_res_t res;

 bytes = offsetof(struct data, ref) + size*sizeof(struct refitem);

 alignment = MPS_PF_ALIGN; /* needed to make it as wide as size_t */

 /* twiddle the value of size to make it aligned */
 bytes = (bytes+alignment-1) & ~(alignment-1);

 do
 {
  res = mps_reserve(&addr, ap, bytes);
  if (res) goto Finish;
  INCCOUNT(RESERVE_COUNT);
  q = (mycell *)addr;
  q->data.tag = MCdata + (mps_word_t) wrapper;
  q->data.assoc = NULL;
  q->data.id = nextid;
  q->data.copycount = 0;
  q->data.numrefs = size;
  q->data.checkedflag = 0;
  q->data.rank = rank;
  q->data.size = bytes;

  for(i=0; i<size; i+=1)
  {
   q->data.ref[i].addr = NULL;
   q->data.ref[i].id   = 0;
  }
 }
 while (!mps_commit(ap, addr, bytes));
 INCCOUNT(ALLOC_COUNT);
 commentif(alloccomments, "allocated id %li at %p.", nextid, q);
 nextid += 1;
 *rvar = q;

Finish:
 return res;
}


mycell *allocone(mps_ap_t ap, int size, mps_rank_t rank) {
 mycell *q;

 die(allocrone(&exfmt_root, ap, size, rank), "Reserve: ");
 q = exfmt_root;
 return q;
}


static mps_res_t myscan(mps_ss_t ss, mps_addr_t base, mps_addr_t limit)
{
 int i;

 INCCOUNT(SCANCALL_COUNT);
 MPS_SCAN_BEGIN(ss)
 {
  while (base < limit)
  {
   mycell *obj = base;
   mps_res_t res;
   mps_addr_t p, q;
   mps_rank_t rank;

   switch (obj->tag & 0x3)
   {
    case MCpad:
     INCCOUNT(SCANPAD_COUNT);
     base = (mps_addr_t) (obj->pad.tag &~ (mps_word_t) 3);
     break;
    case MCdata:
     /* actual scanning is done in here */
     asserts(obj->tag == MCdata + (mps_word_t) wrapper, "scan on bad object");
     asserts(obj->data.id != MCerrorid, "scan on error object");
     rank = obj->data.rank;
     INCCOUNT(SCANOBJ_COUNT);
     commentif(formatcomments, "scan %li at %p.", obj->data.id, base);

     /* make sure to fix the assoc pointer first */
     p = obj->data.assoc;
     if (p != NULL) {
      commentif(fixcomments, "fix %li[assoc]", obj->data.id);
      q = p;
      res = MPS_FIX12(ss, (mps_addr_t *) &p);
      if (res != MPS_RES_OK) return res;
      if (p == NULL) {
       asserts(rank == mps_rank_weak(),
               "non-weak reference fixed to NULL at %p[assoc]", obj);
       commentif(deathcomments, "fixed %p[assoc] to NULL", obj->data.id);
       INCCOUNT(DYING_REFERENCE_COUNT);
      } else if (p != q) {
       asserts(rank != mps_rank_ambig(),
               "ambiguous reference changed by fix at %p[assoc]", obj);
      }
      obj->data.assoc = p;
     }

     for (i=0; i<(obj->data.numrefs); i++)
     {
      p = obj->data.ref[i].addr;
      if (p != NULL)
      {
       /* copy ref to p for fixing, to avoid a pun (although
          the pun would probably work fine almost everywhere)
       */
       commentif(fixcomments, "fix %li[%i] -> %li",
                 obj->data.id, i, obj->data.ref[i].id);
       q = p;
       res = MPS_FIX12(ss, (mps_addr_t *) &p);
       if (p == NULL) {
        asserts(rank == mps_rank_weak(),
                "non-weak reference fixed to NULL at %p[i]", obj);
        commentif(deathcomments, "fixed %li[%i] to NULL", obj->data.id, i);
        INCCOUNT(DYING_REFERENCE_COUNT);
       } else if (p != q) {
        asserts(rank != mps_rank_ambig(),
                "ambiguous reference changed by fix at %p[i]", obj);
       }
       if (res != MPS_RES_OK) return res;
       obj->data.ref[i].addr = p;
      }
     }
     base = (mps_addr_t) ((char *) obj + (obj->data.size));
     break;
    case MCheart:
     INCCOUNT(SCANHEART_COUNT);
     base = (mps_addr_t) ((char *) obj + (obj->heart.size));
     break;
    default:
     asserts(0, "scan: bizarre obj tag at %p.", obj);
   }
  }
 }
 MPS_SCAN_END(ss);
 return MPS_RES_OK;
}


static mps_addr_t myskip(mps_addr_t object)
{
 mycell *obj = object;

 commentif(skipcomments, "skip %p.", object);
 INCCOUNT(SKIP_COUNT);
 switch(obj->tag & 0x3)
 {
  case MCpad:
   return (mps_addr_t) (obj->pad.tag &~ (mps_word_t) 0x3);
  case MCheart:
   return (mps_addr_t) ((char *) obj + (obj->heart.size));
  case MCdata:
   asserts(obj->tag == MCdata + (mps_word_t) wrapper,
    "skip on bad obj at %p.", obj);
   return (mps_addr_t) ((char *) obj + (obj->data.size));
  default:
   asserts(0, "skip: bizarre obj tag at %p.", obj);
   return 0; /* not reached */
 }
}


static void mycopy(mps_addr_t object, mps_addr_t to)
{
 mycell *boj = object;
 mycell *toj = to;

 asserts(boj->tag == MCdata + (mps_word_t) wrapper,
  "copy: non-data object");

 INCCOUNT(COPY_COUNT);
 commentif(formatcomments, "copy: %li: %p -> %p\n",
  boj->data.id, object, to);

 /* this line would be bad, because the objects might overlap,
    and then C doesn't guarantee to do the right thing!

    *toj = *boj;
 */

 memmove(to, object, boj->data.size);
 if (!freeze)
 {
  toj->data.copycount = (toj->data.copycount)+1;
  if (toj->data.copycount > maxcopy) maxcopy = toj->data.copycount;
 }
}


/* pad stores not its size but a pointer to the next object,
   because we know we'll never be asked to copy it
*/

static void mypad(mps_addr_t base, size_t size)
{
 mycell *obj = base;

 asserts(size >= MPS_PF_ALIGN, "pad: size too small.");

 INCCOUNT(PAD_COUNT);
 obj->pad.tag = MCpad + (mps_word_t) ((char *) base + size);
}


static mps_addr_t myisfwd(mps_addr_t object)
{
 mycell *obj = object;

 INCCOUNT(ISFWD_COUNT);
 if ((obj->tag & 3) != MCheart)
 {
  return NULL;
 }
 else
 {
  return obj->heart.obj;
 }
}


static void myfwd(mps_addr_t object, mps_addr_t to)
{
 mycell *obj = object;
 size_t size;

 asserts(((obj->tag & 3) == MCdata) || ((obj->tag & 3) == MCheart),
  "fwd: unexpected object tag at %p.", obj);
 INCCOUNT(FWD_COUNT);

 if ((obj->tag & 3) == MCdata)
 {
  asserts(obj->tag == MCdata + (mps_word_t) wrapper,
   "fwd: bad obj at %p.", obj);
  size = obj->data.size;
 }
 else /* obj->tag == MCheart */
 {
  size = obj->heart.size;
 }
 obj->data.tag = MCheart;
 obj->heart.obj = to;
 obj->heart.size = size;
}


/* ---------------------------------------------------------------
   Access methods for mycell objects
*/

/* set the nth reference of obj to to (n from 0 to size-1) */

void setref(mycell *obj, int n, mycell *to)
{
 asserts(obj->tag == MCdata + (mps_word_t) wrapper,
  "setref: from non-data object at %p", obj);
 asserts((to==NULL)||((to->tag & 3) == MCdata),
  "setref: to non-data object at %p", to);
 asserts(obj->data.numrefs > n, "setref: access beyond object size.");

 obj->data.ref[n].addr = to;
 obj->data.ref[n].id = (to==NULL ? 0 : to->data.id);
}


mycell *getref(mycell *obj, int n)
{
 asserts(obj->tag == MCdata + (mps_word_t) wrapper,
  "getref: from non-data object.");
 asserts(obj->data.numrefs > n, "getref: access beyond object size.");
 return obj->data.ref[n].addr;
}


mps_addr_t getdata(mycell *obj)
{
 return (mps_addr_t) &(obj->data.ref[0]);
}


mps_addr_t getassociated(mps_addr_t addr)
{
  mycell *obj = addr;
  return (mps_addr_t) &(obj->data.ref[1]);
}


long int getid(mycell *obj)
{
 asserts(obj->tag == MCdata + (mps_word_t) wrapper,
  "getid: non-data object.");
 return obj->data.id;
}


long int getcopycount(mycell *obj)
{
 asserts(obj->tag == MCdata + (mps_word_t) wrapper,
  "getcopycount: non-data object.");
 return obj->data.copycount;
}


long int getsize(mycell *obj)
{
 asserts(obj->tag == MCdata + (mps_word_t) wrapper,
  "getsize: non-data object.");
 return obj->data.numrefs;
}


/* ---------------------------------------------------------------
   Now the useful things specially for checking the graph
*/

/* recursively check the graph, starting at an object.
   We do the check twice, so as to restore the
   checkflags to zero.
*/

static void checkloop(mycell *obj, int dir)
{
 mycell *toj;
 int tid;
 int i;

 asserts(obj->tag == MCdata + (mps_word_t) wrapper,
  "checkfrom: non data object in graph at %p.", obj);

 if (obj->data.checkedflag != dir)
 {
  commentif(checkcomments, "checking %p = %li", obj, obj->data.id);

  checkobjcount += 1;

  obj->data.checkedflag = dir;

  if (obj->data.assoc != NULL) {
   toj = obj->data.assoc;
   checkloop(toj, dir);
  }
  for (i=0; i<(obj->data.numrefs); i+=1)
  {
   if (obj->data.ref[i].addr != NULL)
   {
    toj = (obj->data.ref[i].addr);
    tid = (obj->data.ref[i].id);
    asserts(toj->data.id == tid,
     "checkfrom: corrupt graph at %p, %d.", obj, i);
    checkloop(toj, dir);
   }
  }
 }
}


void checkfrom(mycell *obj)
{
 int k;

 freeze = 1; /* suspend counting while checking graph */
 checkobjcount = 0;
 checkloop(obj, 1);
 comment("checkfrom: %li live objects checked", checkobjcount);
 k = checkcomments;
 checkcomments = 0;
 checkloop(obj, 0);
 checkcomments = k;
 comment("checkfrom: graph ok from ID: %li.", obj->data.id);
 freeze = 0; /* resume counting */
}


/* ----------------------------------------------------------
   Now things to reset and display the counters
*/

void resetcounters(void)
{
 int i;
 for (i=0; i < PAD_COUNT+1; i++)
 {
  counters[i]=0;
  prevcounters[i]=0;
  maxcounters[i]=0;
 }
 maxcopy = 0;
}


void updatecounters(void)
{
 int i;
 for (i=0; i < PAD_COUNT+1; i++)
 {
  if (counters[i]-prevcounters[i] > maxcounters[i])
  {
   maxcounters[i]=counters[i]-prevcounters[i];
  }
  prevcounters[i]=counters[i];
 }
}


static void d_c(int i, char *name)
{
 comment("%10d %s", counters[i], name);
}


static void d_mc(int i, char *name)
{
 comment("%10d %s", maxcounters[i], name);
}


void displaycounters(void)
{
 comment("--------");
 comment("Counters:");
 d_c(SCANCALL_COUNT, "scan calls");
 d_c(SCANOBJ_COUNT, "object scans");
 d_c(SCANHEART_COUNT, "heart scans");
 d_c(SCANPAD_COUNT, "pad scans");
 d_c(COPY_COUNT, "copys");
 d_c(SKIP_COUNT, "skips");
 d_c(FWD_COUNT, "fwds");
 d_c(ISFWD_COUNT, "isfwds");
 d_c(PAD_COUNT, "pads");
 d_c(RESERVE_COUNT, "reserve calls");
 d_c(ALLOC_COUNT, "allocations");
 d_c(DYING_REFERENCE_COUNT, "references fixed to NULL");
 comment("--------");
}


void displaymaxcounters(void)
{
 comment("--------");
 comment("Maximum counter values:");
 d_mc(SCANCALL_COUNT, "scan calls");
 d_mc(SCANOBJ_COUNT, "object scans");
 d_mc(SCANHEART_COUNT, "heart scans");
 d_mc(SCANPAD_COUNT, "pad scans");
 d_mc(COPY_COUNT, "copys");
 d_mc(SKIP_COUNT, "skips");
 d_mc(FWD_COUNT, "fwds");
 d_mc(ISFWD_COUNT, "isfwds");
 d_mc(RESERVE_COUNT, "reserve calls");
 d_mc(ALLOC_COUNT, "allocations");
 d_mc(DYING_REFERENCE_COUNT, "references fixed to NULL");
 d_mc(PAD_COUNT, "pads");
 comment("--------");
 comment("max copies of a single object: %li.", maxcopy);
 comment("--------");
}
