/* $Id: //info.ravenbrook.com/project/mps/master/test/test/testlib/lofmt.c#4 $
lofmt.c
   A format for pool class LO
*/

#include "lofmt.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...

 variable      default function

 alloclocomments  comment on allocation (0)
 allowlocopies    allow objects to be copied (1)
*/

int alloclocomments=0;
int allowlocopies=1;

long int nextid=0x2000000;

/* a cell can be one of four things, depending on its type:
  LOpadsingle - a single pad item, MPS_PF_ALIGN in size
  LOpadmulti  - a longer pad item, at least 2*MPS_PF_ALIGN in size
  LOheart     - a broken heart, aka forwarding object
  LOdata      - a real object
*/

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 fmtLO =
{
 MPS_PF_ALIGN,
 &myscan,
 &myskip,
 &mycopy,
 &myfwd,
 &myisfwd,
 &mypad
};

/* in the following, size is the size_t of the data element
   of the lodata structure you want the object to have; i.e.
   the number of bytes of storage you get in the object on top
   of the headers.
*/

locell *alloclo(mps_ap_t ap, size_t size) {
 mps_addr_t p;
 locell *q;
 size_t bytes;
 size_t alignment;

 bytes = offsetof(struct lodata, data) + 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 {
  die(mps_reserve(&p, ap, bytes), "Reserve: ");
  q=p;
  q->data.tag = LOdata;
  q->data.id = nextid;
  q->data.copycount = 0;
  q->data.size = bytes;
 }
 while (!mps_commit(ap, p, bytes));
 commentif(alloclocomments, "allocated id %li at %p.", nextid, q);
 nextid += 1;

 return q;
}

static mps_res_t myscan(mps_ss_t ss, mps_addr_t b, mps_addr_t l) {
 error("Scan method in LO format called!");
 return MPS_RES_OK;
}

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

 switch(obj->tag)
 {
  case LOpadsingle:
   return (mps_addr_t) ((char *) obj + MPS_PF_ALIGN);
  case LOpadmany:
   return obj->padmulti.next;
  case LOheart:
   return (mps_addr_t) ((char *) obj + (obj->heart.size));
  case LOdata:
   return (mps_addr_t) ((char *) obj + (obj->data.size));
  default:
   asserts(0, "loskip: bizarre obj tag at %p.", obj);
   return 0; /* not reached */
 }
}

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

 asserts(allowlocopies, "locopy on LO object");
 asserts(boj->tag == LOdata, "locopy: non-data object");

 memmove(to, object, boj->data.size);
 toj->data.copycount = (toj->data.copycount)+1;
}

/* 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)
{
 locell *obj = base;

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

 if (size == MPS_PF_ALIGN)
 {
  asserts(sizeof(obj->padsingle) <= MPS_PF_ALIGN, "impossible pad");
  obj->padsingle.tag = LOpadsingle;
 }
 else
 {
  asserts(size >= sizeof(struct lopadmulti), "pad: awkward size.");
  obj->padmulti.tag = LOpadmany;
  obj->padmulti.next = (mps_addr_t) ((char *) base + size);
 }
}

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

 if (obj->tag != LOheart)
 {
  return NULL;
 }
 else
 {
  return obj->heart.obj;
 }
}

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

 asserts(obj->tag == LOdata || obj->tag == LOheart,
  "lofwd: unexpected object tag at %p.", obj);

 if (obj->tag == LOdata)
 {
  size = obj->data.size;
 }
 else /* obj->tag == LOheart */
 {
  size = obj->heart.size;
 }
 obj->data.tag = LOheart;
 obj->heart.obj = to;
 obj->heart.size = size;
}

long int getloid(locell *obj)
{
 asserts(obj->tag == LOdata, "getloid: non-data object.");
 return obj->data.id;
}

long int getlocopycount(locell *obj)
{
 asserts(obj->tag == LOdata, "getlocopycount: non-data object.");
 return obj->data.copycount;
}

size_t getlosize(locell *obj)
{
 asserts(obj->tag == LOdata, "getlosize: non-data object.");
 return obj->data.size - offsetof(struct lodata, data);
}