/*m.c - redirection for memory allocation functions*/

/*OSLib---efficient, type-safe, transparent, extensible,\n"
   register-safe A P I coverage of RISC O S*/
/*Copyright  1994 Jonathan Coxhead*/

/*
      OSLib is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 1, or (at your option)
   any later version.

      OSLib is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

      You should have received a copy of the GNU General Public License
   along with this programme; if not, write to the Free Software
   Foundation, Inc, 675 Mass Ave, Cambridge, MA 02139, U S A.
*/

#undef  TRACE
#define TRACE 1

#if TRACE
/*#define XTRACE*/
#endif

/*From CLib*/
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/*From OSLib*/
#include "macros.h"
#include "os.h"
#include "wimp.h"

/*From Support*/
#include "m.h"
#include "riscos.h"
#include "svc.h"
#include "trace.h"

#define JUNK0 0xA5 /*used to initialise the client's part of the block*/
#define JUNK1 0xA7 /*used to initialise our EXTRA bytes*/
#define JUNK2 0xA9 /*used to clear blocks before freeing*/
#define GUARD 0xACCE55ED /*set at the head of each block*/
#define EXTRA 4 /*bytes extra on each block*/

#define m_ASSERT(file, line, c, msg) \
      (!(c)? m_assert (file, line, msg): (void) 0)

typedef struct Block *Block;

struct Block
   {
      int    guard;
      Block  next;
      Block  prev;
      int    size;
      char  *file;
      int    line;
      char   data [UNKNOWN];
   };

#define BLOCK(N) \
      struct \
         {  int    guard; \
            Block  next; \
            Block  prev; \
            int    size; \
            char  *file; \
            int    line; \
            char   data [N]; \
         }

#define sizeof_BLOCK(N) \
      (offsetof (struct Block, data) + (N)*sizeof ((Block) NULL)->data)

static int Byte_Count = 0, Block_Count = 0;

static Block List;
/*------------------------------------------------------------------------*/

static void m_assert
(
   char *file,
   int   line,
   char *msg
)
{
   os_error error;

   trace_f (file, line, "m_assert %s\n", msg);

   error.errnum = 1;
   sprintf (error.errmess, "%s,%d: Assertion failure: %s",
         file, line, msg);

   if (svc_get_mode () == svc_USR)
      os_generate_error (&error);
   else
      xwimp_report_error (&error, NONE, "<<Memory checker>>", NULL);
}
/*------------------------------------------------------------------------*/

static void Check
(
   char *file,
   int  line
)
{
   Block block;
   int   count = 0, size = 0, i;

#ifdef XTRACE
   trace_f (file, line, "m: Check\n");
#endif

   /*To check the list in order of allocation, first find its start.*/
   block = List;
   if (block != NULL)
      while (block->prev != NULL)
         block = block->prev;

   /*Then scan through it.*/
   while (block != NULL)
   {
      count++;
      size += block->size;

      m_ASSERT (file, line, block->guard == GUARD,
            "Invalid header guard word");
      m_ASSERT (file, line, block->size != 0,
            "Size nought block");
      m_ASSERT (file, line, strlen (block->file) < FILENAME_MAX,
            "Overlong source file name");

      if (block->next != NULL)
         m_ASSERT (file, line, block->next->prev == block,
               "Invalid next-block pointer");

      if (block->prev != NULL)
         m_ASSERT (file, line, block->prev->next == block,
               "Invalid previous-block pointer");

      for (i = 0; i < EXTRA; i++)
         m_ASSERT (file, line, block->data [block->size + i] == JUNK1,
               "Overwritten trailing guard bytes");

      block = block->next;
   }

   m_ASSERT (file, line, count == Block_Count,
         "Wrong number of blocks");
   m_ASSERT (file, line, size == Byte_Count,
         "Wrong block size");
}
/*------------------------------------------------------------------------*/

static void *Alloc
(
   char *file,
   int   line,
   int   size,
   int   filler
)
{
   Block block;

   Check (file, line);

   if (size != 0 && (block = malloc (sizeof_BLOCK (size + EXTRA))) != NULL)
   {
      Block_Count++;
      Byte_Count += size;

      /*guard*/
      block->guard = GUARD;

      /*next*/
      block->next = NULL;

      /*prev*/
      block->prev = List;

      /*size*/
      block->size = size;

      /*file*/
      block->file = file;

      /*line*/
      block->line = line;

      /*data*/
      memset (block->data,        filler, size);
      memset (block->data + size, JUNK1,  EXTRA);

      /*Link it to the rest of the world.*/
      if (List != NULL) List->next = block;
      List = block;

#ifdef XTRACE
      trace_f (file _ line _ "m_ALLOC (%d) -> 0x%X\n" _
            size _ block->data);
#endif
      return block->data;
   }
   else
   {  /*0 bytes requested, or allocation failed.*/
#ifdef XTRACE
      trace_f (file _ line _ "m_ALLOC (%d): no store\n" _ size);
#endif
      return NULL;
   }
}
/*------------------------------------------------------------------------*/

void *m_alloc (char *file, int line, int size)
      {return Alloc (file, line, size, JUNK0);}
/*------------------------------------------------------------------------*/

void *m_calloc (char *file, int line, int count, int size)
      {return Alloc (file, line, count*size, 0);}
/*------------------------------------------------------------------------*/

void m_free
(
   char *file,
   int   line,
   void *ptr,
   int size
)
{
   Block block;

   NOT_USED (file) /*maybe*/
   NOT_USED (line) /*maybe*/
   NOT_USED (size)

   Check (file, line);

   if (ptr != NULL)
   {
      /*block->data == ptr, therefore*/
      block = (Block) ((char *) ptr - offsetof (struct Block, data));

      if (block->next != NULL)
         block->next->prev = block->prev;

      if (block->prev != NULL)
         block->prev->next = block->next;

      if (List == block) List = block->prev;

      Block_Count--;
      Byte_Count -= block->size;

      memset (block, JUNK2, sizeof_BLOCK (block->size));

      free (block);

#ifdef XTRACE
      trace_f (file _ line _ "m_FREE (0x%X)\n" _ ptr);
#endif
   }
   else
   {
#ifdef XTRACE
      trace_f (file _ line _ "m_FREE (NULL)\n");
#endif
   }
}
/*------------------------------------------------------------------------*/

void *m_realloc
(
   char *file,
   int   line,
   void *ptr,
   int   old_size,
   int   size
)
{
   if (ptr != NULL && size != 0)
   {
      void *tmp;

      if ((tmp = m_alloc (file, line, size)) == NULL)
         return NULL;

      memcpy (tmp, ptr, size);

      m_free (file, line, ptr, old_size);

      return tmp;
   }
   else if (size != 0)
      return m_alloc (file, line, size);
   else
   {
      /*ptr != NULL*/
      m_free (file, line, ptr, old_size);
      return NULL;
   }
}
/*------------------------------------------------------------------------*/

void m_summary
(
   char *file,
   int   line
)
{
   Block block;
   int   i = 0, size = 0;

   trace_f (file _ line _ "m_SUMMARY\n");

   Check (file, line);

   /*To print out the list in order of allocation, first find its start.*/
   block = List;
   if (block != NULL)
      while (block->prev != NULL)
         block = block->prev;

   /*Then scan through it.*/
   while (block != NULL)
   {
      if (i%20 == 0)
         trace_f (NULL _ 0 _ "   %-*s   %*s   %s,%s\n" _
               DEC_WIDTH _ "block no" _ DEC_WIDTH _ "bytes" _
               "file" _ "line");

      trace_f (NULL _ 0 _ "   %-*d   %*d   %s,%d\n" _
            DEC_WIDTH _ i _ DEC_WIDTH _ block->size _
            block->file _ block->line);

      i++;
      size += block->size;
      block = block->next;
   }

   trace_f (file _ line _ "total: Block_Count %d, Byte_Count %d\n" _
         Block_Count _ Byte_Count);
}
/*------------------------------------------------------------------------*/

void *m_validate_address
(
   char *file,
   int   line,
   void *ptr
)
{
   Block block;

   trace_f (file _ line _ "m_VALIDATE_ADDRESS\n");

   Check (file, line);

   /*Scan through it.*/
   for (block = List; block != NULL; block = block->prev)
      if ((void *) block->data <= ptr &&
            ptr < (void *) &block->data [block->size])
         return ptr;

   m_ASSERT (file, line, FALSE, "Use of invalid pointer");
   return SKIP;
}
