/* Free a block of memory allocated by `malloc'.
   Copyright 1990, 1991, 1992 Free Software Foundation
		  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

#ifdef CHKR_HOOK
/* Debugging hook for free.  */
void (*__free_hook) __P ((__ptr_t __ptr));
#endif /* CHKR_HOOK */

#ifdef CHECKER
#include "errlist.h"
#endif /* CHECKER */
/* List of blocks allocated by memalign.  */
struct alignlist *_aligned_blocks = NULL;

#ifdef CHKR_HEAPBITMAP
static int can_set_right; /* can call chkr_set_right after _free_internal */
#endif

/* Return memory to the heap.
   Like `free' but don't call a __free_hook if there is one.  */
void
_free_internal (ptr)
     __ptr_t ptr;
{
  int type;
  size_t block, blocks;
  register size_t i;
  struct list *prev, *next;
  
#if CHECKER  
  chkr_errno = E_UNKNOWN;
  if(ptr < (__ptr_t)_heapbase || ptr > ADDRESS(_heaplimit))
  {
    chkr_errno = E_BADADDR;
    chkr_perror();
    return;
  }
#endif /* CHECKER */
    
  block = BLOCK (ptr);

#if CHECKER  
  /* check if this block is busy */
  switch(_heapinfo[block].status.state)
  {
   case MDHEAD:
   case MDINTERN:
     break;
   case MDFREE:
     chkr_errno = E_FREEFBLOCK;
     chkr_perror();
     return;
   default:
     chkr_errno = E_BADADDR;
     chkr_perror();
     return;
   }
#endif /* CHECKER */

  type = _heapinfo[block].busy.type;
  switch (type)
    {
    case 0:
#if CHECKER     
      /* Check if ptr is lined up */
      if(ADDRESS(block)!=ptr)
      {
       chkr_errno = E_BADADDR;
       chkr_perror();
       return;
      }
#endif /* CHECKER */
      
#ifdef CHKR_MSTAT      
      /* Get as many statistics as early as we can.  */
      --_chunks_used;
      _bytes_used -= _heapinfo[block].busy.info.size * BLOCKSIZE;
      _bytes_free += _heapinfo[block].busy.info.size * BLOCKSIZE;
#endif /* CHKR_MSTAT */
#ifdef CHECKER      
      if(_heapinfo[block].busy.info.size == 0)
      {
        chkr_errno = E_NULLBSIZE;
        chkr_perror();
        return;
      }
      for(i = block + _heapinfo[block].busy.info.size - 1; i >= block; i--)
       _heapinfo[i].status.state = MDFREE;
#endif /* CHECKER */

      /* Find the free cluster previous to this one in the free list.
	 Start searching at the last block referenced; this may benefit
	 programs with locality of allocation.  */
      i = _heapindex;
      if (i > block)
	while (i > block)
	  i = _heapinfo[i].free.prev;
      else
	{
	  do
	    i = _heapinfo[i].free.next;
	  while (i > 0 && i < block);
	  i = _heapinfo[i].free.prev;
	}

      /* Determine how to link this block into the free list.  */
      if (block == i + _heapinfo[i].free.size)
	{
	  /* Coalesce this block with its predecessor.  */
	  _heapinfo[i].free.size += _heapinfo[block].busy.info.size;
	  block = i;
	}
      else
	{
	  /* Really link this block back into the free list.  */
	  _heapinfo[block].free.size = _heapinfo[block].busy.info.size;	  
	  _heapinfo[block].free.next = _heapinfo[i].free.next;
	  _heapinfo[block].free.prev = i;
	  _heapinfo[i].free.next = block;
	  _heapinfo[_heapinfo[block].free.next].free.prev = block;
	  ++_chunks_free;
	}

      /* Now that the block is linked in, see if we can coalesce it
	 with its successor (by deleting its successor from the list
	 and adding in its size).  */
      if (block + _heapinfo[block].free.size
	      	 == _heapinfo[block].free.next)
	{
	  _heapinfo[block].free.size
	    += _heapinfo[_heapinfo[block].free.next].free.size;
#if CHECKER	    
	  _heapinfo[block].status.state = MDFREE;
#endif /* CHECKER */
	  _heapinfo[block].free.next
	    = _heapinfo[_heapinfo[block].free.next].free.next;
	  _heapinfo[_heapinfo[block].free.next].free.prev = block;
	  --_chunks_free;
	}

#ifdef CHKR_HEAPBITMAP
      can_set_right = 1;	/* all is correct */
#endif      	

      /* Now see if we can return stuff to the system.  */
      blocks = _heapinfo[block].free.size;
      if (blocks >= FINAL_FREE_BLOCKS && block + blocks == _heaplimit
	  && (*__morecore) (0) == ADDRESS (block + blocks))
	{
	  register size_t bytes = blocks * BLOCKSIZE;
	  _heaplimit -= blocks;
	  (*__morecore) (-bytes);
	  _heapinfo[_heapinfo[block].free.prev].free.next
	    = _heapinfo[block].free.next;
	  _heapinfo[_heapinfo[block].free.next].free.prev
	    = _heapinfo[block].free.prev;
	  block = _heapinfo[block].free.prev;
#ifdef CHKR_MSTAT	  
	  --_chunks_free;
	  _bytes_free -= bytes;
#endif /* CHKR_MSTAT */	  
#ifdef CHKR_HEAPBITMAP
	  can_set_right = 0; 	/* zone is forgotten. So, don't set right */
#endif /* CHKR_HEAPBITMAP */
	}

      /* Set the next search to begin at this block.  */
      _heapindex = block;
      break;

    default:
      /* Check if ptr is lined up*/
      if((size_t)ptr %(1 << type) != 0)
      {
       chkr_errno = E_BADADDR;
       chkr_perror();
       return;
      }

      /* Get the address of the first free fragment in this block.  */
      prev = (struct list *) ((char *) ADDRESS (block) +
			   (_heapinfo[block].busy.info.frag.first << type));
		
#if CHECKER
      /* Test if this fragment is free */
      next=prev;
      for( i=_heapinfo[block].busy.info.frag.nfree; i > 0; i--)
      {
       if(next==ptr)
       {
        chkr_errno = E_FREEFFRAG;
        chkr_perror();
        return;
       }
       next=next->next;
      }
#endif /* CHECKER */
      
#ifdef CHKR_MSTAT
      /* Do some of the statistics.  */
      --_chunks_used;
      _bytes_used -= 1 << type;
      ++_chunks_free;
      _bytes_free += 1 << type;
#endif /* CHKR_MSTAT */
      
      if (_heapinfo[block].busy.info.frag.nfree == (BLOCKSIZE >> type) - 1)
	{
	  /* If all fragments of this block are free, remove them
	     from the fragment list and free the whole block.  */
	  next = prev;
	  for (i = 1; i < (size_t) (BLOCKSIZE >> type); ++i)
	    next = next->next;
	  prev->prev->next = next;
	  if (next != NULL)
	    next->prev = prev->prev;
#if CHECKER
	  else
	    if(prev->prev == &_fraghead[type])
	      _fraghead[type].prev = NULL;
	    else
	      _fraghead[type].prev=prev->prev;
#endif /* CHECKER */
	  _heapinfo[block].busy.type = 0;
	  _heapinfo[block].busy.info.size = 1;

#ifdef CHKR_MSTAT
	  /* Keep the statistics accurate.  */
	  ++_chunks_used;
	  _bytes_used += BLOCKSIZE;
	  _chunks_free -= BLOCKSIZE >> type;
	  _bytes_free -= BLOCKSIZE;
#endif /* CHKR_MSTAT */

	  _free_internal (ADDRESS (block));
	}
      else if (_heapinfo[block].busy.info.frag.nfree != 0)
	{
	  /* If some fragments of this block are free, link this
	     fragment into the fragment list after the first free
	     fragment of this block. */
	  next = (struct list *) ptr;
	  next->next = prev->next;
	  next->prev = prev;
	  prev->next = next;
	  if (next->next != NULL)
	    next->next->prev = next;
#if CHECKER
	  else
	    _fraghead[type].prev=next;
#endif /* CHECKER */
	  ++_heapinfo[block].busy.info.frag.nfree;
	}
      else
	{
	  /* No fragments of this block are free, so link this
	     fragment into the fragment list and announce that
	     it is the first free fragment of this block. */
	  prev = (struct list *) ptr;
	  _heapinfo[block].busy.info.frag.nfree = 1;	  
	  _heapinfo[block].busy.info.frag.first = (unsigned long int)
	    ((unsigned long int) ((char *) ptr - (char *) NULL)
	     % BLOCKSIZE >> type);
	  prev->next = _fraghead[type].next;
	  prev->prev = &_fraghead[type];
	  prev->prev->next = prev;
	  if (prev->next != NULL)
	    prev->next->prev = prev;
#if CHECKER
	  else
	    _fraghead[type].prev = prev;
#endif /* CHECKER */
	}
      break;
    }
}

/* Return memory to the heap.  */
void
free (ptr)
     __ptr_t ptr;
{
  register struct alignlist *l;
#ifdef CHKR_HEAPBITMAP
  __ptr_t real_ptr = ptr;
  unsigned int real_size;
#endif /* CHKR_HEAPBITMAP */

  if (ptr == NULL)
    return;

#if CHECKER
  if(!__malloc_initialized)	/* Imagine you call free before malloc */
  {
    chkr_errno = E_FREEBMALLOC;
    chkr_perror();
    return;
  }
#endif /* CHECKER */

  for (l = _aligned_blocks; l != NULL; l = l->next)
    if (l->aligned == ptr)
      {
	l->aligned = NULL;	/* Mark the slot in the list as free.  */
	ptr = l->exact;
	break;
      }
      
#ifdef CHKR_GARBAGE
  ptr -= be_red_zone + sizeof(struct hdr_red_zone);
  real_size = ((struct hdr_red_zone*)ptr)->real_size;
#endif /* CHKR_GARBAGE */

#ifdef CHKR_HOOK
  if (__free_hook != NULL)
    (*__free_hook) (ptr);
  else
#endif /* CHKR_HOOK */  
    _free_internal (ptr);
    
#if 0
  /* Clear memory, so that no harzardous pointer should exist */
  if (chkr_errno == E_UNKNOWN)
    memset(ptr, 0, ((struct hdr_red_zone*)ptr)->full_size);
#endif
#ifdef CHKR_HEAPBITMAP
  if (can_set_right) 	/* don't set right in case of error */
  {
    chkr_set_right(real_ptr, real_size, CHKR_UN);
    can_set_right = 0;
  }
#endif /* CHKR_HEAPBITMAP */    
}
