/* Change the size of a block allocated by `malloc'.
   Copyright 1990, 1991, 1992, 1993, 1994 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
#include <string.h>


#define min(A, B) ((A) < (B) ? (A) : (B))


/* Resize the given region to the new size, returning a pointer
   to the (possibly moved) region.  This is optimized for speed;
   some benchmarks seem to indicate that greater compactness is
   achieved by unconditionally allocating and copying to a
   new region.  This module has incestuous knowledge of the
   internals of both free and malloc. */
void *
_gnu_realloc (void *ptr, size_t size, malloc_state *ms, malloc_funcs *mf)
{
  void * result;
  int type;
  size_t block, blocks, oldlimit;
  long (*s)(void*,size_t);

  if (size == 0)
    {
      _gnu_free (ptr, ms, mf);
      return _malloc_internal (0, ms, mf);
    }
  else if (ptr == NULL)
    return _malloc_internal (size, ms, mf);

  block = BLOCK (ptr);

  type = ms->_heapinfo[block].busy.type;
  switch (type)
    {
    case 0:
      /* Maybe reallocate a large block to a small fragment.  */
      if (size <= BLOCKSIZE / 2)
	{
	  result = _malloc_internal (size, ms, mf);
	  if (result != NULL)
	    {
	      memcpy (result, ptr, size);
	      _free_internal (ptr, ms, mf);
	      return result;
	    }
	}

      /* The new size is a large allocation as well;
	 see if we can hold it in place. */
      blocks = BLOCKIFY (size);
      if (blocks < ms->_heapinfo[block].busy.info.size)
	{
	  /* The new size is smaller; return
	     excess memory to the free list. */
	  ms->_heapinfo[block + blocks].busy.type = 0;
	  ms->_heapinfo[block + blocks].busy.info.size
	    = ms->_heapinfo[block].busy.info.size - blocks;
	  ms->_heapinfo[block].busy.info.size = blocks;
	  /* We have just created a new chunk by splitting a chunk in two.
	     Now we will free this chunk; increment the statistics counter
	     so it doesn't become wrong when _free_internal decrements it.  */
	  ++ms->_chunks_used;
	  _free_internal (ADDRESS (block + blocks), ms, mf);
	  result = ptr;
	}
      else if (blocks == ms->_heapinfo[block].busy.info.size)
        {
	    /* No size change necessary.  */
	    result = ptr;
	}
      else
	{
	  /* Won't fit, so allocate a new region that will.
	     Free the old region first in case there is sufficient
	     adjacent free space to grow without moving. */
	  blocks = ms->_heapinfo[block].busy.info.size;
	  /* Prevent free from actually returning memory to the system.  */
	  oldlimit = ms->_heaplimit;
	  ms->_heaplimit = 0;
	  s = mf->free_memory;
	  mf->free_memory = NULL;
	  _free_internal (ptr, ms, mf);
	  mf->free_memory = s;
	  ms->_heaplimit = oldlimit;
	  result = _malloc_internal (size, ms, mf);
	  if (result == NULL)
	    {
	      /* Now we're really in trouble.  We have to unfree
		 the thing we just freed.  Unfortunately it might
		 have been coalesced with its neighbors.  */
	      if (ms->_heapindex == block)
		(void) _malloc_internal (blocks * BLOCKSIZE, ms, mf);
	      else
		{
		  void * previous;
		  previous = _malloc_internal ((block - ms->_heapindex) * BLOCKSIZE, ms, mf);
		  (void) _malloc_internal (blocks * BLOCKSIZE, ms, mf);
		  s = mf->free_memory;
		  mf->free_memory = NULL;
		  _free_internal (previous, ms, mf);
		  mf->free_memory = s;
		}
	      return NULL;
	    }
	  if (ptr != result)
	    memmove (result, ptr, blocks * BLOCKSIZE);
	}
      break;

    default:
      /* Old size is a fragment; type is logarithm
	 to base two of the fragment size.  */
      if (size > (size_t) (1 << (type - 1)) &&
	  size <= (size_t) (1 << type))
        {
	    /* The new size is the same kind of fragment.  */
	    result = ptr;
	}
      else
	{
	  /* The new size is different; allocate a new space,
	     and copy the lesser of the new size and the old. */
	  result = _malloc_internal (size, ms, mf);
	  if (result == NULL)
	      return NULL;
	  memcpy (result, ptr, min (size, (size_t) 1 << type));
	  _gnu_free (ptr, ms, mf);
	}
      break;
    }

  return result;
}


void *
_realloc (void *ptr, size_t size, malloc_state *ms, malloc_funcs *mf)
{
	void * ret;

	_m_acquire_sem(ms);
	ret = _gnu_realloc(ptr, size, ms, mf);
	_m_release_sem(ms);

	return ret;
}
