/* memalign
   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
*/

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

#include "machine.h"
#include "errlist.h"
#include "message.h"

/* get real_size bytes */
__ptr_t
_memalign(size_t alignment, size_t real_size)
{
  size_t round_size;
  struct malloc_header *tmp;
  struct malloc_header *tmp1;
  int i;
  uint lost;	/* bytes lost due to alignment */

#if 1
  if (real_size == 0)
    return (__ptr_t)0;
#endif      
  if (__malloc_initialized == 0)
    {
      __malloc_initialized = 1;
      chkr_initialize();	/* which called init_morecore */
    }
  if (alignment == 0)
    {
      alignment = 1;
      chkr_printf(M_NULL_ALIGN);
    }
  /* Avoid boring problems */
  while (alignment & (LITTLE_SPACE-1))
    alignment <<= 1;
    
  round_size = real_size + af_red_zone + be_red_zone;
  round_size = (round_size + (LITTLE_SPACE-1)) & ~(LITTLE_SPACE-1);

  /* Try to find a free block, in the lists of free blocks */
  tmp1 = NULL_HEADER;
  /* Begins with the smallest list */
#ifdef NO_HEAPINDEX  
  for(i = log_size(round_size); i < HASH_SIZE; i++)
#else /* !NO_HEAPINDEX */
  for(i = log_size(round_size); i <= _heapindex; i++)  
#endif /* NO_HEAPINDEX */
    {
      if (_heapinfo[i] == NULL_HEADER)
        continue;	/* empty list */
      tmp = _heapinfo[i];
      for(tmp = _heapinfo[i]; tmp != NULL_HEADER; tmp = tmp->info.free.next)
        {
          if (tmp->size < round_size)
            continue;	/* block is too little */
          lost = alignment - (((int)tmp + HEADER_SIZE + be_red_zone) % alignment);
          if (lost == alignment)
            lost = 0;
          if (lost != 0)
            while (lost < (HEADER_SIZE + LITTLE_SPACE))
              lost += alignment;  /* because we can't insert an header */
          /* bytes lost due to alignment */
          if (tmp->size - lost < round_size || tmp->size < lost)
            continue;
          if ((tmp->size - lost) - round_size != 0 &&
              (tmp->size - lost) - round_size < (HEADER_SIZE + LITTLE_SPACE))
            continue;	/* we can't use an header after */
          /* This is now OK: the block tmp will be used */
          tmp->state = MDBUSY;	/* This block will be used */
          /* remove the block from the list */
          if (tmp->info.free.next)
            tmp->info.free.next->info.free.prev = tmp->info.free.prev;
          if (tmp->info.free.prev)
            tmp->info.free.prev->info.free.next = tmp->info.free.next;
          if (_heapinfo[i] == tmp)
            _heapinfo[i] = tmp->info.free.next;
          /* Must we create a block before ? */
          if (lost != 0)
            {
              /* Create a new block */
              tmp1 = (struct malloc_header*)((char*)tmp + lost);
              tmp1->prev = tmp;
              tmp1->next = tmp->next;
              if (tmp->next)
                tmp->next->prev = tmp1;
              tmp->next = tmp1;
              tmp1->size = tmp->size - lost;  /* Not true at this time */
              tmp->size = lost - HEADER_SIZE;
              if(_lastblock == tmp)
                _lastblock = tmp1;
              tmp1->state = MDBUSY;
              _internal_free(tmp);
              tmp=tmp1;
            }
          if (tmp->size > round_size)
            {
              /* split the block: tmp1 is a new block */
              tmp1 = (struct malloc_header*)((char*)tmp + round_size + HEADER_SIZE);
              tmp1->prev = tmp;
              tmp1->next = tmp->next;
              if (tmp->next)
                tmp->next->prev = tmp1;
              tmp->next = tmp1;        
              tmp1->size = tmp->size - HEADER_SIZE - round_size;
              tmp->size = round_size;
              if (_lastblock == tmp)
                _lastblock = tmp1;
              tmp1->state = MDBUSY;
              _internal_free(tmp1);
            }
#ifndef NO_HEAPINDEX        
          /* Compute _heapindex, if necessary */
          if(_heapindex == i && _heapinfo[i] == NULL_HEADER)
            while(_heapinfo[_heapindex] == NULL_HEADER)
              {
                if(_heapindex)
                  _heapindex--;
                else
                  break;
              }
#endif /* NO_HEAPINDEX */
          goto done;
        }  
    }
  /* No free block: must allocate memory */
  tmp1 = NULL_HEADER;	/* to free */
  /* first, try the last block */
  if (_lastblock->state == MDFREE)
    {
      tmp = _lastblock;
      lost = alignment - (int)tmp % alignment;
      if (lost != (HEADER_SIZE + be_red_zone))
        while (lost < (2*HEADER_SIZE + LITTLE_SPACE + be_red_zone))
          lost += alignment;
      i = log_size(tmp->size);
      tmp->size = round_size;
      /* remove the block from the list */
      if (tmp->info.free.next)
        tmp->info.free.next->info.free.prev = tmp->info.free.prev;
      if (tmp->info.free.prev)
        tmp->info.free.prev->info.free.next = tmp->info.free.next;
      if (_heapinfo[i] == tmp)
        _heapinfo[i] = tmp->info.free.next;
      if (lost != (HEADER_SIZE + be_red_zone))
        {
          morecore(lost - 2*HEADER_SIZE - LITTLE_SPACE - be_red_zone - tmp->size);
          tmp->size = lost - 2*HEADER_SIZE - LITTLE_SPACE - be_red_zone;
          tmp1 = tmp;
        }
      else
        {
          morecore(round_size - tmp->size);
          tmp->size = round_size;
          goto done;
        }
    }
  tmp = (struct malloc_header*)morecore(0);
  lost = alignment - (int)tmp % alignment;
  if (lost != (HEADER_SIZE + be_red_zone))
    while (lost < (2*HEADER_SIZE + LITTLE_SPACE + be_red_zone))
      lost += alignment;
  if (lost != (HEADER_SIZE + be_red_zone))
    {
      morecore(lost - HEADER_SIZE - be_red_zone);
      tmp->prev = _lastblock;
      tmp->next = NULL_HEADER;
      if (_lastblock)
        tmp->prev->next = tmp;
      else
        _firstblock = tmp;
      _lastblock = tmp;
      tmp->size = lost - 2 * HEADER_SIZE - be_red_zone;
      tmp->state = MDBUSY;
      tmp1 = tmp;	/* to free */
      tmp = (struct malloc_header*)morecore(round_size + HEADER_SIZE);
    }
  else
     morecore(round_size + HEADER_SIZE);
  tmp->size = round_size;
  tmp->prev = _lastblock;
  if (_lastblock)
    _lastblock->next = tmp;
  else
    tmp->next = NULL_HEADER;
  _lastblock = tmp;
  if(!_firstblock)
    _firstblock = tmp;
  if (tmp1 != NULL_HEADER)
    _internal_free(tmp1);
 done:
  /* all is right */
  tmp->state = MDBUSY;
  tmp->garbage_t = POINT_NOT;
  tmp->info.busy.real_size = real_size;
#ifdef CHKR_SAVESTACK
  chkr_save_stack(	/* save the stack */
    	(__ptr_t*)((u_int)tmp + HEADER_SIZE + round_size - af_red_zone),
    	0, /* number of frame to forget. with 0, show malloc */
    	af_red_zone / sizeof(void*)); /* number of frames to save */
#endif /* CHKR_SAVESTACK */
#ifdef CHKR_HEAPBITMAP
  chkr_set_right((__ptr_t)tmp + HEADER_SIZE + be_red_zone, real_size, CHKR_WO);
#endif /* CHKR_HEAPBITMAP */
  return (__ptr_t)tmp + HEADER_SIZE + be_red_zone;
}

__ptr_t
valloc (size_t size)
{
#ifdef CHKR_STACKBITMAP
  chkr_check_addr(&size, sizeof(size_t), CHKR_RO);
#endif
  return _memalign (PAGESIZE, size);
}

__ptr_t
memalign(size_t alignment, size_t real_size)
{
#ifdef CHKR_STACKBITMAP
  chkr_check_addr(&alignment, sizeof(size_t), CHKR_RO);
  chkr_check_addr(&real_size, sizeof(size_t), CHKR_RO);
#endif
  return _memalign(alignment, real_size);
}
