/* Memory allocator `malloc'.
   Copyright 1990, 1991, 1992, 1993, 1994, 1995 Free Software Foundation, Inc.
		  Written May 1989 by Mike Haertel.

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library 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
Library General Public License for more details.

You should have received a copy of the GNU Library General Public
License along with this library; see the file COPYING.LIB.  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 mike@ai.mit.edu,
   or (US mail) as Mike Haertel c/o Free Software Foundation.  */

#ifndef	_MALLOC_INTERNAL
#define _MALLOC_INTERNAL
#include "malloc.h"
#endif



/* Aligned allocation.  */
static void *
align (size_t size, malloc_state *ms, malloc_funcs *mf)
{
  void * result;
  unsigned long int adj;

  result = (*mf->morecore) (size, ms);

  adj = (unsigned long int) ((unsigned long int) result) % BLOCKSIZE;
  if (adj != 0)
    {
      adj = BLOCKSIZE - adj;
      (void) (*mf->morecore) (adj, ms);
      result = (char *) result + adj;
    }

  if (mf->after_morecore_hook)
    (*mf->after_morecore_hook) ();

  
  return result;
}



/* Set everything up and remember that we have.  */
static int
initialize (malloc_state *ms, malloc_funcs *mf)
{
  if (mf->malloc_initialize_hook)
    (*mf->malloc_initialize_hook) ();

  ms->heapsize = HEAP / BLOCKSIZE;
  ms->_heapinfo = (malloc_info *) align (ms->heapsize * sizeof (malloc_info), ms, mf);
  if (ms->_heapinfo == NULL)
    return 0;
  memset (ms->_heapinfo, 0, ms->heapsize * sizeof (malloc_info));
  ms->_heapinfo[0].free.size = 0;
  ms->_heapinfo[0].free.next = ms->_heapinfo[0].free.prev = 0;
  ms->_heapindex = 0;
  ms->_heapbase = (char *) ms->_heapinfo;

  /* Account for the _heapinfo block itself in the statistics.  */
  ms->_bytes_used = ms->heapsize * sizeof (malloc_info);
  ms->_chunks_used = 1;

  ms->malloc_initialized = 1;

  return 1;
}

/* Get neatly aligned memory, initializing or
   growing the heap info table as necessary. */
static void *
morecore (size_t size, malloc_state *ms, malloc_funcs *mf)
{
  void * result;
  malloc_info *newinfo, *oldinfo;
  size_t newsize;

  result = align (size, ms, mf);
  if (result == NULL)
    return NULL;

  /* Check if we need to grow the info table.  */
  if ((size_t) BLOCK ((char *) result + size) > ms->heapsize)
    {
      newsize = ms->heapsize;
      while ((size_t) BLOCK ((char *) result + size) > newsize)
	newsize *= 2;
      newinfo = (malloc_info *) align (newsize * sizeof (malloc_info), ms, mf);
      if (newinfo == NULL)
	{
	  (*mf->morecore) (-size, ms);
	  return NULL;
	}
      memcpy (newinfo, ms->_heapinfo, ms->heapsize * sizeof (malloc_info));
      memset (&newinfo[ms->heapsize], 0,
	      (newsize - ms->heapsize) * sizeof (malloc_info));
      oldinfo = ms->_heapinfo;
      newinfo[BLOCK (oldinfo)].busy.type = 0;
      newinfo[BLOCK (oldinfo)].busy.info.size
	= BLOCKIFY (ms->heapsize * sizeof (malloc_info));
      ms->_heapinfo = newinfo;
      /* Account for the _heapinfo block itself in the statistics.  */
      ms->_bytes_used += newsize * sizeof (malloc_info);
      ++ms->_chunks_used;
      _free_internal (oldinfo, ms, mf);
      ms->heapsize = newsize;
    }

  ms->_heaplimit = BLOCK ((char *) result + size);
  return result;
}

/* Allocate memory from the heap.  */
void *
_malloc_internal (size, ms, mf)
     size_t size;
     malloc_state *ms;
     malloc_funcs *mf;
{
  void * result;
  size_t block, blocks, lastblocks, start;
  register size_t i;
  struct list *next;

  size_t osize = size;

  /* ANSI C allows `malloc (0)' to either return NULL, or to return a
     valid address you can realloc and free (though not dereference).

     It turns out that some extant code (sunrpc, at least Ultrix's version)
     expects `malloc (0)' to return non-NULL and breaks otherwise.
     Be compatible.  */

#if	0
  if (size == 0)
    return NULL;
#endif

  if (size >= LONG_MAX)   /* can't allocate greater than LONG_MAX bytes */
      return NULL;


  if (!ms->malloc_initialized)
    if (!initialize (ms, mf))
      return NULL;

  if (size < sizeof (struct list))
    size = sizeof (struct list);

#ifdef SUNOS_LOCALTIME_BUG
  if (size < 16)
    size = 16;
#endif

  /* Determine the allocation policy based on the request size.  */
  if (size <= BLOCKSIZE / 2)
    {
      /* Small allocation to receive a fragment of a block.
	 Determine the logarithm to base two of the fragment size. */
      register size_t log = 1;
      --size;
      while ((size /= 2) != 0)
	++log;

      /* Look in the fragment lists for a
	 free fragment of the desired size. */
      next = ms->_fraghead[log].next;
      if (next != NULL)
	{
	  /* There are free fragments of this size.
	     Pop a fragment out of the fragment list and return it.
	     Update the block's nfree and first counters. */
	  result = (void *) next;
	  next->prev->next = next->next;
	  if (next->next != NULL)
	    next->next->prev = next->prev;
	  block = BLOCK (result);
	  if (--ms->_heapinfo[block].busy.info.frag.nfree != 0)
	    ms->_heapinfo[block].busy.info.frag.first = (unsigned long int)
	      ((unsigned long int) ((char *) next->next - (char *) NULL)
	       % BLOCKSIZE) >> log;

	  /* Update the statistics.  */
	  ++ms->_chunks_used;
	  ms->_bytes_used += 1 << log;
	  --ms->_chunks_free;
	  ms->_bytes_free -= 1 << log;
	}
      else
	{
	  /* No free fragments of the desired size, so get a new block
	     and break it into fragments, returning the first.  */
	  result = _malloc_internal (BLOCKSIZE, ms, mf);
	  if (result == NULL)
	      return NULL;

	  /* Link all fragments but the first into the free list.  */
	  for (i = 1; i < (size_t) (BLOCKSIZE >> log); ++i)
	    {
	      next = (struct list *) ((char *) result + (i << log));
	      next->next = ms->_fraghead[log].next;
	      next->prev = &ms->_fraghead[log];
	      next->prev->next = next;
	      if (next->next != NULL)
		next->next->prev = next;
	    }

	  /* Initialize the nfree and first counters for this block.  */
	  block = BLOCK (result);
	  
	  ms->_heapinfo[block].busy.type = log;
	  ms->_heapinfo[block].busy.info.frag.nfree = i - 1;
	  ms->_heapinfo[block].busy.info.frag.first = i - 1;

	  ms->_chunks_free += (BLOCKSIZE >> log) - 1;
	  ms->_bytes_free += BLOCKSIZE - (1 << log);
	  ms->_bytes_used -= BLOCKSIZE - (1 << log);
	}
    }
  else
    {
      /* Large allocation to receive one or more blocks.
	 Search the free list in a circle starting at the last place visited.
	 If we loop completely around without finding a large enough
	 space we will have to get more memory from the system.  */
      blocks = BLOCKIFY (size);
      start = block = ms->_heapindex;
      while (ms->_heapinfo[block].free.size < blocks)
	{
	  block = ms->_heapinfo[block].free.next;
	  if (block == start)
	    {
	      /* Need to get more from the system.  Check to see if
		 the new core will be contiguous with the final free
		 block; if so we don't need to get as much.  */
	      block = ms->_heapinfo[0].free.prev;
	      lastblocks = ms->_heapinfo[block].free.size;
	      if (ms->_heaplimit != 0 && block + lastblocks == ms->_heaplimit &&
		  (*mf->morecore) (0, ms) == ADDRESS (block + lastblocks) &&
		  (morecore (((blocks - lastblocks) * BLOCKSIZE), ms, mf)) != NULL)
		{
 		  /* Which block we are extending (the `final free
 		     block' referred to above) might have changed, if
 		     it got combined with a freed info table.  */
 		  block = ms->_heapinfo[0].free.prev;
  		  ms->_heapinfo[block].free.size += (blocks - lastblocks);
		  ms->_bytes_free += (blocks - lastblocks) * BLOCKSIZE;
		  continue;
		}

	      result = morecore (blocks * BLOCKSIZE, ms, mf);
	      if (result == NULL)
		return NULL;
	      block = BLOCK (result);

	      ms->_heapinfo[block].busy.type = 0;
	      ms->_heapinfo[block].busy.info.size = blocks;
	      ++ms->_chunks_used;
	      ms->_bytes_used += blocks * BLOCKSIZE;
	      return result;
	    }
      }

      /* At this point we have found a suitable free list entry.
	 Figure out how to remove what we need from the list. */
      result = ADDRESS (block);
      if (ms->_heapinfo[block].free.size > blocks)
	{
	  /* The block we found has a bit left over,
	     so relink the tail end back into the free list. */
	  ms->_heapinfo[block + blocks].free.size
	    = ms->_heapinfo[block].free.size - blocks;
	  ms->_heapinfo[block + blocks].free.next
	    = ms->_heapinfo[block].free.next;
	  ms->_heapinfo[block + blocks].free.prev
	    = ms->_heapinfo[block].free.prev;
	  ms->_heapinfo[ms->_heapinfo[block].free.prev].free.next
	    = ms->_heapinfo[ms->_heapinfo[block].free.next].free.prev
	    = ms->_heapindex = block + blocks;
	}
      else
	{
	  /* The block exactly matches our requirements,
	     so just remove it from the list. */
	  ms->_heapinfo[ms->_heapinfo[block].free.next].free.prev
	    = ms->_heapinfo[block].free.prev;
	  ms->_heapinfo[ms->_heapinfo[block].free.prev].free.next
	    = ms->_heapindex = ms->_heapinfo[block].free.next;
	  --ms->_chunks_free;
	}

      ms->_heapinfo[block].busy.type = 0;
      ms->_heapinfo[block].busy.info.size = blocks;
      ++ms->_chunks_used;
      ms->_bytes_used += blocks * BLOCKSIZE;
      ms->_bytes_free -= blocks * BLOCKSIZE;

      /* Mark all the blocks of the object just allocated except for the
	 first with a negative number so you can find the first block by
	 adding that adjustment.  */
      while (--blocks > 0)
	ms->_heapinfo[block + blocks].busy.info.size = -blocks;
    }

  return result;
}


void *
_malloc (size_t size, malloc_state *ms, malloc_funcs *mf)
{
    void * ptr;
    
    _m_acquire_sem(ms);
    ptr = _malloc_internal(size, ms, mf);
    _m_release_sem(ms);

    return ptr;
}


