/*
 *  fxmemmon.c -- mmout, mmpause, mmshow, and internal functions.
 *
 *   This file contains memory monitoring code.  It is compiled by inclusion
 *   in fxtra.c if MemMon is defined.  When MemMon is undefined, most of the
 *   "MMxxxx" entry points are defined as null macros in rt.h.
 */

#include "..\h\config.h"
#include "..\h\rt.h"
#include "rproto.h"



#ifdef MemMon
/*
 * Prototypes.
 */

hidden	novalue mmcmd		Params((word addr, word len, int c));
hidden	novalue mmdec		Params((uword n));
hidden	novalue mmforget	Params((noargs));
hidden	novalue mmlen		Params((word n, int c));
hidden	novalue mmnewline	Params((noargs));
hidden	novalue mmrefresh	Params((noargs));
hidden	novalue mmsizes		Params((int c));
hidden	novalue mmstatic	Params((noargs));
hidden	novalue MMOut		Params((char *prefix, char *msg));

static FILE *monfile = NULL;	/* output file pointer */
static char *monname = NULL;	/* output file name */

static word llen = 0;		/* current output line length */

static char typech[MaxType+1];	/* output character for each type */

/* Define size of curlength table, and bias needed to access it. */
/* Assumes all type codes are printable characters (or space).   */
/* Smaller table is used if not EBCDIC.                          */
#if !EBCDIC
#define CurSize (127 - ' ')
#define CurBias ' '
#else					/* !EBCDIC */
#define CurSize 256
#define CurBias 0
#endif					/* !EBCDIC */

static word curlength[CurSize];	/* current length for each output character */

/* line limit: start a new line when a command goes beyond this column */
#define LLIM 70

/* mmchar(c): output character c and update the column counter */
#define mmchar(c) (llen++,putc((c),monfile))

/* mmspace(): output unneeded whitespace whitespace following a command */
/*  define as "mmchar(' ')" for readable files, or as "0" for compact ones */
#define mmspace() 0

/*
 * mmout(s) - write the given string to the MemMon file.
 */

FncDcl(mmout,1)
   {
   char sbuf[MaxCvtLen];
   int t;

   if ((t = defstr(&Arg1, sbuf, &emptystr)) == Error) 
      RunErr(0, NULL);
   /*
    * Make sure Arg1 is a C-style string.
    */
   if (t == NoCvt)
      qtos(&Arg1, sbuf);
   MMOut("", StrLoc(Arg1));
   Arg0 = nulldesc;
   Return;
   }

/*
 * mmpause(s) - pause MemMon displaying string s.
 */

FncDcl(mmpause,1)
   {
   char sbuf[MaxCvtLen];
   int t;

   if ((t = defstr(&Arg1, sbuf, &emptystr)) == Error) 
      RunErr(0, NULL);
   if (StrLen(Arg1) == 0)
      MMOut("; ", "programmed pause");
   else {
      /*
       * Make sure Arg1 is a C-style string.
       */
      if (t == NoCvt)
         qtos(&Arg1, sbuf);
      MMOut("; ", StrLoc(Arg1));
      }
   Arg0 = nulldesc;
   Return;
   }

/*
 * mmshow(x, s) - alter MemMon display of x depending on s.
 */

FncDcl(mmshow,2)
   {
   char sbuf[MaxCvtLen];

   /*
    * Default Arg2 to the empty string and make sure it is a C-style string.
    */
   switch (defstr(&Arg2, sbuf, &emptystr)) {

      case Cvt:   /* Already converted to a C-style string */
         break;

      case Defaulted:
      case NoCvt:
         qtos(&Arg2, sbuf);
         break;

      case Error:
         RunErr(0, NULL);
      }

   MMShow(&Arg1, StrLoc(Arg2));
   Arg0 = nulldesc;
   Return;
   }

/*
 * MMInit(filename) - initialization.
 *
 *  Memory monitoring is activated if the environment variable MEMMON is
 *  non-null.  Its value names the output file;  or, under Unix, a value
 *  beginning with "|" specifies a command to which the output is piped.
 *
 *  If MemMon is defined on a system lacking environment variables,
 *  monitoring is always activated and output is to the file "memmon.out".
 */

novalue MMInit(filename)
char *filename;
   {
   int i;
   FILE *f;
   char time_buf[26];

#ifdef EnvVars
   monname = getenv("MEMMON");
   if (monname == NULL || strlen(monname) == 0)
      return;
#else					/* EnvVars */
   monname = "memmon.out";
#endif					/* EnvVars */

#if UNIX
   if (monname[0] == '|')
      f = popen(monname+1, WriteText);
   else
#endif					/* UNIX */

      f = fopen(monname, WriteText);

   if (f == NULL) {
      fprintf(stderr, "MEMMON: cannot open %s\n", monname);
      fflush(stderr);
      exit(ErrorExit);
      }


   getctime(time_buf);
   fprintf(f, "##  Icon MemMon output\n");
   fprintf(f, "#\n");
   fprintf(f, "#   program: %s\n", filename);
   fprintf(f, "#   date:    %s\n", time_buf);

   for (i = 0; i <= MaxType; i++)
      typech[i] = '?';	/* initialize with error character */

#ifdef LargeInts
   typech[T_Bignum]  = 'i';	/* long integer */
#endif					/* LargeInts */

   typech[T_Real]    = 'r';	/* real number */
   typech[T_Cset]    = 'c';	/* cset */
   typech[T_File]    = 'f';	/* file block */
   typech[T_Record]  = 'R';	/* record block */
   typech[T_Tvsubs]  = 'u';	/* substring trapped variable */
   typech[T_External]= 'E';	/* external block */

   typech[T_List]    = 'L';	/* list header block */
   typech[T_Lelem]   = 'l';	/* list element block */

   typech[T_Table]   = 'T';	/* table header block */
   typech[T_Telem]   = 't';	/* table element block */
   typech[T_Tvtbl]   = 'e';	/* table elem trapped variable*/

   typech[T_Set]     = 'S';	/* set header block */
   typech[T_Selem]   = 's';	/* set element block */

   typech[T_Slots]   = 'h';	/* set/table hash slots */

   typech[T_Coexpr]  = 'X';	/* co-expression block (static region) */
   typech[T_Refresh] = 'x';	/* co-expression refresh block */

   /*
    * codes used elsewhere but not shown here:
    *    in the static region: 'A' = alien (malloc block), 'F' = free
    *    in the string region: '"' = string
    */

   /*
    * Set monfile to indicate that memmon is active.  Don't set it earlier
    * than this, or we'll loop trying to trace the garbage collection that
    * creates the buffer space.
    */
   monfile = f;
   mmrefresh();			/* show current state */
   fflush(monfile);		/* force it out */
   }

/*
 * MMTerm(part1, part2) - terminate memory monitoring.
 *  part1 and part2 are concatentated to form an explanatory message.
 */

novalue MMTerm(part1, part2)
char *part1, *part2;
   {
   FILE *f;

   if (monfile == NULL)
      return;
   mmnewline();
   mmsizes('=');		/* make a final check on region sizes */

   if (*part1 || *part2)	/* if any reason given, write it as comment */
      fprintf(monfile, "# %s%s\n", part1, part2);

   f = monfile;
   monfile = NULL;	/* so we don't try to show the freeing of the buffer */

#if UNIX
   if (monname[0] == '|')
      pclose(f);
   else
#endif					/* UNIX */
      fclose(f);
   }

/*
 * MMStat(a, n, c) - note static block at a, length n, represented by char 'c'.
 * Output values are in basic units (typically words).
 */
novalue MMStat(a, n, c)
char *a;
word n;
int c;
   {
#ifndef FixedRegions
   if (monfile == NULL)
      return;
   mmcmd(DiffPtrs(a, statbase) / MMUnits, n / MMUnits, c);
#endif					/* FixedRegions */
   }

/*
 * MMAlc(len, type) - note an allocation at the end of the block region.
 */

novalue MMAlc(len, type)
word len;
int type;
   {
   if (monfile == NULL)
      return;
   mmcmd((word)(-1), len / MMUnits, typech[type]);
   }

/*
 * MMStr(len) - note a string allocation at the end of the string region.
 */

novalue MMStr(slen)
word slen;
   {
   if (monfile == NULL)
      return;
   mmcmd((word)(-1), slen, '"');
   }

/*
 * MMBGC() - begin garbage collection.
 */

novalue MMBGC(region)
int region;
   {
   if (monfile == NULL)
      return;
   mmsizes('=');			/* write current sizes */
   fprintf(monfile, "%d{\n", region);	/* indicate start of g.c. */
   fflush(monfile);
   mmforget();				/* clear memory of block sizes */
   }

/*
 * MMEGC() - end garbage collection.
 */

novalue MMEGC()
   {
   if (monfile == NULL)
      return;
   mmnewline();
   fprintf(monfile, "}\n");	/* indicate end of marking */
   mmrefresh();			/* redraw regions after compaction */
   fprintf(monfile, "!\n");	/* indicate end of g.c. */
   fflush(monfile);
   }

/*
 * MMMark(block, type) - mark indicated block during garbage collection.
 */

novalue MMMark(block, type)
char *block;
int type;
   {
   if (monfile == NULL)
      return;
   mmcmd(DiffPtrs(block, blkbase) / MMUnits, (word)BlkSize(block) / MMUnits,
      typech[type]);
   }

/*
 * MMSMark - Mark String.
 */

novalue MMSMark(saddr, slen)
char *saddr;
word slen;
   {
   if (monfile == NULL)
      return;
   mmcmd(DiffPtrs(saddr, strbase), slen, '"');
   }

/*
 * MMOut(prefix, msg) - write the prefix and message to the MemMon output file.
 */

static novalue MMOut(prefix, msg)
char *prefix, *msg;
   {
   if (monfile == NULL)
      return;
   mmnewline();
   fprintf(monfile, "%s%s\n", prefix, msg);
   }

/*
 * MMShow(d, s) - redraw block indicated by descriptor d according to flags
 *  in s.
 */

novalue MMShow(d, s)
dptr d;
char *s;
   {
   char *block;
   uword addr;
   word len;
   char cmd, tch;

   if (monfile == NULL)
      return;
   if (Qual(*d)) {
      /*
       *  Show a string.
       */
/*
      if ((uword)StrLoc(*d)<(uword)strbase || (uword)StrLoc(*d)>=(uword)strend)
*/
      if (!InRange(strbase,StrLoc(*d),strend))
         return;	/* ignore if outside string region */
      addr = DiffPtrs(StrLoc(*d), strbase);
      len = StrLen(*d);
      cmd = '$';
      tch = '"';
      }
   else if (Type(*d)==T_Coexpr) {
      /*
       *  Show a coexpression block, which will be in the static region.
       */
      block = (char *)BlkLoc(*d);
      addr = DiffPtrs(block, statbase) / MMUnits;
      len = BlkSize(block) / MMUnits;
      cmd = 'Y';
      tch = typech[T_Coexpr];
      }
   else if (Pointer(*d)) {
      /*
       *  Show something in the block region.
       */
      block = (char *)BlkLoc(*d);
/*
      if ((uword)block < (uword)blkbase || (uword)block >= (uword)blkfree)
*/
      if (!InRange(blkbase,block,blkfree))
         return;	/* ignore if outside block region */
      addr = DiffPtrs(block, blkbase) / MMUnits;
      len = BlkSize(block) / MMUnits;
      cmd = '%';
      tch = typech[Type(*d)];
      }

   mmdec(addr);			/* address */
   mmchar('+');
   mmlen(len, cmd);		/* length, and $ Y or % command */
   if (s && *s)
      mmchar(*s);		/* color flag from mmshow call */
   else 
      mmchar('r');		/* default color is 'r' (redraw) */
   mmchar(tch);			/* block type character */
   if (llen >= LLIM)
      mmnewline();
   else
      mmspace();
   }

/*
 * mmrefresh() - redraw screen, initially or after garbage collection.
 */

static novalue mmrefresh()
   {
   char *p;
   word n;

   mmnewline();
   mmsizes('<');			/* signal start of screen refresh */
   mmnewline();
   mmforget();				/* clear memory of past sizes */
   mmstatic();				/* show the static region */
   mmnewline();
   for (p = blkbase; p < blkfree; p += n)
      MMAlc(n = BlkSize(p), (int)BlkType(p));/* block region */
   mmnewline();
   MMStr(DiffPtrs(strfree, strbase));	/* string region */
   mmnewline();
   fprintf(monfile, ">\n");		/* signal end of refresh */
   mmsizes('=');			/* confirm region sizes */
   mmforget();				/* clear memory of past sizes */
   }

/*
 *  mmstatic() - recap the static region (stack, coexprs, aliens, free)
 *   (this function is empty under FixedRegions)
 */
static novalue mmstatic()
   {
#ifndef FixedRegions
   HEADER *p;
   char *a;
   int h;
   word n;

   for (p = (HEADER *)statbase; (uword)p < (uword)(HEADER *)statfree;
      p += p->s.bsize) {
         a = (char *)(p + 1);
         n = (p->s.bsize - 1) * sizeof(HEADER);
         h = *(int *)a;
         if (h == T_Coexpr || a == (char *)stack)
            MMStat(a, n, 'X');		/* coexpression block */
         else if (h == FREEMAGIC)
            MMStat(a, n, 'F');		/* free block */
         else
            MMStat(a, n, 'A');		/* alien block */
         }
   a = (char *)p;
   if (a < statend)
      MMStat(a, (word)(statend-a), 'F');/* rest of static region is free */
#endif					/* FixedRegions */
   }

/*
 * mmsizes(c) - output current region sizes, with initial character c.
 * If c is '<', the unit size is written ahead of it.
 */
static novalue mmsizes(c)
int c;
   {
   mmnewline();
   if (c == '<')
      fprintf(monfile, "%d", MMUnits);
   fprintf(monfile, "%c %lu:%lu/%lu %lu:%lu/%lu %lu:%lu/%lu\n", c,
      /* static region; show as full, actual amount is unknown */
      (unsigned long)statbase,
      (unsigned long)DiffPtrs(statend, statbase),
      (unsigned long)DiffPtrs(statend, statbase),
      /* string region */
      (unsigned long)strbase,
      (unsigned long)DiffPtrs(strfree, strbase),
      (unsigned long)DiffPtrs(strend, strbase),
      /* block region */
      (unsigned long)blkbase,
      (unsigned long)DiffPtrs(blkfree, blkbase),
      (unsigned long)DiffPtrs(blkend, blkbase));
   }

/*
 * mmcmd(addr, len, c) - output a memmon command.
 *  If addr is < 0, it is omitted.
 *  If len matches the previous value for command c, it is also omitted.
 *  If the output fills the line, a following newline is written.
 */

static novalue mmcmd(addr, len, c)
word addr, len;
int c;
   {
   if (addr >= 0) {
      mmdec((uword)addr);
      mmchar('+');
      }
   mmlen(len, c);
   if (llen >= LLIM)
      mmnewline();
   else
      mmspace();
   }

/*
 * mmlen(n, c) - output length n with character c.
 * Omit the length if it matches the previous value for c.
 */
static novalue mmlen(n, c)
word n;
int c;
   {
   if (n != curlength[c-CurBias])
      mmdec((uword)(curlength[c-CurBias] = n));
   mmchar(c); 
   }

/*
 * mmdec(n) - output a decimal value, updating the line length.
 */
static novalue mmdec (n)
uword n;
   {
   if (n > 9)
      mmdec(n / 10);
   n %= 10;
   mmchar('0'+(int)n);
   }

/*
 * mmnewline() - output a newline and reset the line length.
 */
static novalue mmnewline()
   {
   if (llen > 0)  {
      putc('\n', monfile);
      llen = 0;
      }
   }

/*
 * mmforget() - clear the history of remembered lengths.
 */
static novalue mmforget()
   {
   int c;

   for (c = 0; c < CurSize; c++)
      curlength[c] = -1;
   }
#else					/* MemMon */
static char x;			/* avoid empty module */
#endif					/* MemMon */
