/* smalloc implementation (simple malloc, which doesn't use [s]brk)
   Copyright 1994 Tristan Gingold
		  Written January 1994 by Tristan Gingold

This program 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 2 of the
License, or (at your option) any later version.

This library 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 program; see the file COPYING.  If not, write to
the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

   The author may be reached (Email) at the address marc@david.saclay.cea.fr,
   or (US/French mail) as Tristan Gingold 
   			  8 rue Parmentier
   			  F91120 PALAISEAU
   			  FRANCE
*/
#include <sys/types.h>
#include "checker.h"
#define NEED_MM
#include "machine.h"

struct sys_malloc_header;

struct sys_malloc_header
{
  struct sys_malloc_header *prev;	/* the next malloc_header, or NULL_HEADER */
  struct sys_malloc_header *next;	/* the previous malloc_header, or NULL_HEADER */
  size_t size;			/* the total size */
  char state;			/* state, see MD* */
  char pad[3];			/* currently unused */
};

#define SYS_NULL_HEADER (struct sys_malloc_header*)0
#define SYS_HEADER_SIZE (sizeof(struct sys_malloc_header))
/* The smallest space that malloc can allocate. Is it useful ? */
#define LITTLE_SPACE (sizeof(int)) /* must be a power of 2 */

/* state possibilities */
#define MDFREE 1	/* block is free */
#define MDBUSY 2	/* block is busy */

static void *morecore(int size);

struct sys_malloc_header *_sys_firstblock;
struct sys_malloc_header *_sys_lastblock;

/* malloc: return the address of a block of size bytes.
 * malloc(0) returns an address
 */ 
void *
sys_malloc(size_t real_size)
{
  struct sys_malloc_header *tmp;
  struct sys_malloc_header *tmp1;

  real_size = (real_size + sizeof(int) - 1) & ~(sizeof(int)-1);
  
  if (_sys_firstblock == SYS_NULL_HEADER)
    {
      tmp = (struct sys_malloc_header*)morecore(SYS_HEADER_SIZE + real_size);
      tmp->state = MDBUSY;
      tmp->prev = tmp->next = SYS_NULL_HEADER;
      _sys_firstblock = _sys_lastblock = tmp;
      tmp->size = real_size;
      return (void *)tmp + SYS_HEADER_SIZE;
    }
  for (tmp = _sys_firstblock; tmp != SYS_NULL_HEADER; tmp = tmp->next)
    {
      if (tmp->state != MDFREE || tmp->size < real_size)
        continue;
      if (tmp->size == real_size)
        {
          tmp->state = MDBUSY;
          return (void *)tmp + SYS_HEADER_SIZE;
        }
      if (tmp->size - real_size < (SYS_HEADER_SIZE + LITTLE_SPACE))
        continue;	/* can't create a new block */
      tmp1 = (struct sys_malloc_header*)((uint)tmp + SYS_HEADER_SIZE + real_size);
      tmp1->size = tmp->size - real_size - SYS_HEADER_SIZE;
      tmp->size = real_size;
      tmp1->next = tmp->next;
      tmp1->prev = tmp;
      tmp->next = tmp1;
      if (tmp1->next)
        tmp1->next->prev = tmp1;
      else
        _sys_lastblock = tmp1;
      tmp1->state = MDFREE;
      return (void *)tmp + SYS_HEADER_SIZE;
    }
  if (_sys_lastblock->state == MDFREE)
    chkr_abort();
  tmp = (struct sys_malloc_header*)morecore(real_size + SYS_HEADER_SIZE);
  tmp->prev = _sys_lastblock;
  tmp->next = SYS_NULL_HEADER;
  tmp->state = MDBUSY;
  _sys_lastblock->next = tmp;
  _sys_lastblock = tmp;
  tmp->size = real_size;
  return (void *)tmp + SYS_HEADER_SIZE;
}

void
sys_free(void * ptr)
{
 struct sys_malloc_header *tmp;
 struct sys_malloc_header *tmp1; 
 
 /* ANSI-C test */
 if (ptr == (void *)0)
   return;
   
 /* Compute the address of the header from ptr */
 tmp = (struct sys_malloc_header*)(ptr - SYS_HEADER_SIZE);

 /* try to coalise this block with its next one */
 if ( (tmp1 = tmp->next) && tmp->next->state == MDFREE)
   {
     tmp->size += tmp1->size + SYS_HEADER_SIZE;
     tmp->next = tmp1->next;
     if (tmp->next)
       tmp->next->prev = tmp;
     if (_sys_lastblock == tmp1)
      _sys_lastblock = tmp;
   }
 
 /* try to coalise this block with its predecessor */
 if ( (tmp1 = tmp->prev) && tmp->prev->state == MDFREE)
   {
     tmp1->size += tmp->size + SYS_HEADER_SIZE;
     tmp1->next = tmp->next;
     if (tmp->next)
       tmp->next->prev = tmp1;
     if (_sys_lastblock == tmp)
       _sys_lastblock = tmp1;
     tmp = tmp1;
   }

  /* Can we free memory ? */
 if(_sys_lastblock == tmp)
   {
     /* Updates _lastblock */
     _sys_lastblock = tmp->prev;
     if (_sys_lastblock)
       _sys_lastblock->next = SYS_NULL_HEADER;
     /* Is there only one block ? */
     if (_sys_firstblock == tmp)
       _sys_firstblock = SYS_NULL_HEADER;
     /* Free memory */
     morecore(-(tmp->size + SYS_HEADER_SIZE));
     return;	/* That's all */
   }
 tmp->state = MDFREE;
 return;
}

void *
sys_realloc (void * ptr, size_t size)
{
  void *result;
  
  if (ptr == (void*)0)
    return sys_malloc(size);
    
  if (size == 0)
    {
      sys_free (ptr);
      return sys_malloc (0);
    }
    
   /* Very simple realloc implementation 
      But easy to do and bugs free ( I hope !) */
   result = sys_malloc (size);
   if (result != (void*)0)
     {
       memcpy (result, ptr, size);
       sys_free (ptr);
     }
   return result;
}

static uint current_size=0;
static uint current_pages=0;

static void *
morecore(int size)
{
 int pages = (current_size + size + PAGESIZE-1) / PAGESIZE;
 
 if (pages > current_pages)
   {
     mmap((char*)(MM_MEM + current_pages * PAGESIZE),
     	  (pages - current_pages) * PAGESIZE,
     	  MM_PROT, MM_FLAGS, MM_FILE, 0);
     current_pages = pages;
   }
 else if (pages < current_pages)
   {
     munmap((char*)(MM_MEM + pages * PAGESIZE),
            (current_pages - pages) * PAGESIZE);
     current_pages = pages;
   }
 current_size += size;
 return (void*)(MM_MEM + current_size - size);
}

#if 0
void
chkr_dump_sbusy()
{
 struct sys_malloc_header *tmp;

 tmp = _sys_firstblock;
 if (tmp == SYS_NULL_HEADER)
     return;
   
 while (tmp != NULL)
   {
     switch(tmp->state)
       {
         case MDBUSY: printf("BUSY: ");
         	break;
         case MDFREE: printf("FREE: ");
         	break;
       }
     printf("block at 0x%x, size %db\n", tmp, tmp->size);
     tmp = tmp->next;
   }
}
#endif /* 0 */