/*---------------------------------------------------------------------------*
 *  pmalloc.c  *
 *                                                                           *
 *  Copyright 2007, 2008 Nuance Communciations, Inc.                               *
 *                                                                           *
 *  Licensed under the Apache License, Version 2.0 (the 'License');          *
 *  you may not use this file except in compliance with the License.         *
 *                                                                           *
 *  You may obtain a copy of the License at                                  *
 *      http://www.apache.org/licenses/LICENSE-2.0                           *
 *                                                                           *
 *  Unless required by applicable law or agreed to in writing, software      *
 *  distributed under the License is distributed on an 'AS IS' BASIS,        *
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 
 *  See the License for the specific language governing permissions and      *
 *  limitations under the License.                                           *
 *                                                                           *
 *---------------------------------------------------------------------------*/




/* this source file is only used when PORTABLE_DINKUM_MEM_MGR is defined
 */
#ifdef PORTABLE_DINKUM_MEM_MGR

#include <stdlib.h>
#include <string.h> /* for memset */
#include "pmalloc.h"
#include "passert.h"
#include "ptypes.h"
#include "plog.h"

#undef malloc
#undef calloc
#undef realloc
#undef free

#ifdef __cplusplus
extern "C"
{
#endif

  /*
   * There are two controlling options within this scheme:
   *
   * STATIC_MEMORY_POOL: When defined, there is a static array from which memory is
   * allocated.  The size of this array is defined at compile time.  When undefined
   * (the default), the memory is allocated via malloc().  This code works under PSOS and
   * PSOSIM, but has not been tested anywhere else (4March02).
   * VOYAGER_KERNEL_MEMORY: When defined for the Voyager platform, it is similar to the
   * non-static memory pool, but the memory buffer is pre-defined, and is simply pointed
   * at by the pmalloc initializer.
   * RTXC_PARTITION_MEMORY: When defined for the RTXC operating system, uses a static kernel
   * partition resource for the memory chunk.
   * VOYAGER_KERNEL_MEMORY and RTXC_PARTITION_MEMORY are mutually exclusive and take precedence
   * over STATIC_MEMORY.
   *
  
   * the standard off-the-shelf Dinkumware software is pretty slow, primarily due to
   * scanning the free-memory linked list in PortFree(). If SPEEDUP is defined, then
   * split the memory pool into imaginary 'bins', and keep track of the first free list
   * entry in each bin. Then the linked list lookup can be MUCH faster, as you can
   * start very close to the final destination in the linked list.
   *
   * (If SPEEDUP_COMPARE is defined, then run BOTH the standard technique and the
   * speedup technique and compare the results.)
   */
  
  /* malloc function */
  _STD_BEGIN
  
  /* data *******************************************************************************/
  
#if defined(PORTABLE_DINKUM_MEM_MGR) || defined(PORTABLE_FIXED_SIZE_MEM_BLOCK_SCHEME)
  /* Verify that memory pool actually was created, because of the lack of structure, this is accessed externally */
  ESR_ReturnCode memory_pool_creation_status = ESR_FATAL_ERROR;
#endif
  
  /* static data */
  _Altab  _Aldata  = {0}; /* heap initially empty */
  psize_t  _Size_block = {SIZE_BLOCK}; /* preferred _Getmem chunk */
  
  /* Memory pool size */
#define MEM_SIZE_MB( mbytes )   ((mbytes) * 1024 * 1024 )
  
#ifndef MEM_SIZE
  /* If not defined on the command line, use default values. */
#define MEM_SIZE    MEM_SIZE_MB( 5 )
#endif
  
  /* Memory pool initialized */
  static int pmallocInitted = FALSE;  /* TRUE once initialized */
  
#ifdef STATIC_MEMORY_POOL
  /* The memory pool can either be statically allocated or require a one-time system malloc.
   * For VTB, the system was taking 2 seconds to zero the static memBuffer[] array at
   * boot time, since it's in the BSS segment. Therefore, for VTB, it is better to allocate
   * at run time.
   */
  static char memBuffer[MEM_SIZE];
#else
  static char *memBuffer;
#endif
  
  static psize_t memSize = MEM_SIZE;
  
  /* Memory pool free list */
  /* partition memory range into 'bins', and keep track of the first
   * free list entry in each bin. This is to speed up the linked-list search
   * that takes place when memory is freed.
   */
#define BIN_BITS         14   /* smaller number ==> more bins */
#define BIN_SIZE      16384   /* 2 ^ BIN_BITS */
  
#define __NUM_MEM_BINS(memPoolSize)  (((memPoolSize)/BIN_SIZE) + 5) /* 5 = extra for roundoff */
#define GET_MEM_BIN( _ptr_ )   (int)(((unsigned int)_ptr_ - (unsigned int)&memBuffer[0]) >> BIN_BITS)
  
#define NUM_MEM_BINS  __NUM_MEM_BINS(MEM_SIZE)
static _Cell				*binsFirstFreeCell[NUM_MEM_BINS+1] = {0};
  static psize_t    numMemBins;
  
  /* Memory Pool sbrk/getmem variables */
  
  static char *__heap_ptr = NULL;
  static char *__heap_end = NULL;
  static int  maxMemUsed = 0;
  
  /* Memory Pool initialization and _GetMem functions ************************************/
  
#if _USE_EXISTING_SYSTEM_NAMES
#define _Sbrk sbrk
#endif
  
  _STD_BEGIN
  
  void *_Sbrk(int incr)
  {
    char *ret;
    
    /* Subtract 1 from __heap_ptr so that the left hand side of the comparison evaluates to the address of the
       last address of the requested memory block */
    if ((__heap_ptr + incr - 1) > __heap_end) return(void *) - 1;
    
    ret = __heap_ptr;
    __heap_ptr += incr;
    maxMemUsed += incr;
    return (void *)ret;
  }
  
  void *_Getmem(psize_t size)
  { /* allocate raw storage */
    void *p;
    int isize = size;
    
    return (isize <= 0 || (p = _Sbrk(isize)) == (void *) - 1
            ? 0 : p);
  }
  _STD_END
  
  /* PortMallocInit() : initialize memory pool. There is no initialization needed for
   * a static memory pool. Otherwise, perform a one-time malloc from the OS.
   */
  void PortMallocInit(void)
  {
#if defined STATIC_MEMORY_POOL
    memSize = MEM_SIZE;
#else
    /* TODO: is malloc of binsFirstFreeCell safe under PSOS in all conditions ? */
    memBuffer    = (char *)malloc(memSize);
#if defined(PORTABLE_DINKUM_MEM_MGR) || defined(PORTABLE_FIXED_SIZE_MEM_BLOCK_SCHEME)
    if (memBuffer != NULL) /* For external access, check comment at top */
      memory_pool_creation_status = ESR_SUCCESS;
#endif
    numMemBins    = __NUM_MEM_BINS(memSize);
#endif /* #ifdef VOYAGER_KERNEL_MEMORY */
    
    passert(memBuffer != NULL);
    passert(binsFirstFreeCell != NULL);
    
    __heap_ptr = &memBuffer[0];
    __heap_end = &memBuffer[memSize-1];
    
    /* set initted flag so we only do this once */
    pmallocInitted = TRUE;
    maxMemUsed = 0;
    
    memset(&_Aldata, 0, sizeof(_Altab));
    
    memset(binsFirstFreeCell, 0, sizeof(_Cell*)*(NUM_MEM_BINS + 1));
  }
  
  void PortMallocTerm(void)
  {
#ifndef STATIC_MEMORY_POOL
    memSize = 0;
    free(memBuffer);
    memBuffer = NULL;
#if defined(PORTABLE_DINKUM_MEM_MGR) || defined(PORTABLE_FIXED_SIZE_MEM_BLOCK_SCHEME)
    memory_pool_creation_status = ESR_FATAL_ERROR; /* For external access, check comment at top */
#endif
#endif
    pmallocInitted = FALSE;
  }
  
  /* PortGetMaxMemUsed() : return the maximum real memory allocated.
   * There is another function of the same name in pmemory.cpp, for tracking
   * psos block memory. It uses #ifdef MEM_PSOS_BLOCK_SCHEME to enable.
   */
  int PortMallocGetMaxMemUsed(void)
  {
    return maxMemUsed;
  }
  
  /* PortMallocSetPoolSize( psize_t size ) : define size of memory pool.
   */
  void PortMallocSetPoolSize(psize_t size)
  {
#if !defined(STATIC_MEMORY_POOL) && !defined(VOYAGER_KERNEL_MEMORY) && !defined(RTXC_PARTITION_MEMORY)
    if (!pmallocInitted)
    {
      memSize = size;
    }
#else
    (void)size;
#endif
  }
  
  /* PortMallocGetPoolSize() : return size of memory pool.
   */
  psize_t PortMallocGetPoolSize(void)
  {
#if defined STATIC_MEMORY_POOL
    return MEM_SIZE;
#else
    return memSize;
#endif
  }
  
  /* debug *******************************************************************************/
  
  /* xalloc.h internal header - debugging components */
#if DEBUG
  
  int _OK_Cell(_Cell *p)
  {
    passert(SIZE_CELL <= p->_Size);
    return 1;
  }
  
  typedef struct _DB_Altab
  {
    psize_t total_heap;
    psize_t total_alloc;
    psize_t total_free;
  }
  _DB_Altab;
  
  _DB_Altab _DB_Altab_object = {0};
  
  void _UPD_Altab(psize_t d_heap, psize_t d_alloc, psize_t d_free)
  {
    _DB_Altab *pd = &_DB_Altab_object;
    pd->total_heap += d_heap;
    pd->total_alloc += d_alloc;
    pd->total_free += d_free;
  }
  
  int _OK_Altab(_Altab *p)
  {
    _DB_Altab *pd = &_DB_Altab_object;
    _Cell *q;
    psize_t total_free = 0;
    if (p->_Head == 0)
      return 1;
    for (q = p->_Head; q != 0; q = q->_Next)
    {
      total_free += q->_Size;
      _OK_Cell(q);
      if (q->_Next != 0)
      {
        passert(_PTR_NORM((char *)q + q->_Size) <=
                _PTR_NORM((char *)q->_Next));
        passert(_PTR_NORM(q) < _PTR_NORM(q->_Next));
      }
    }
    passert(pd->total_heap == pd->total_alloc + pd->total_free);
    passert(total_free == pd->total_free);
    return 1;
  }
#endif /* DEBUG */
  
  /* allocation functions ***************************************************************/
  
  static _Cell **findmem(psize_t size)
  { /* find storage */
    _Cell *q, **qb;
    
    for (; ;)
    { /* check freed space first */
      if ((qb = _Aldata._Plast) == 0)
      { /* take it from the top */
        for (qb = &_Aldata._Head; *qb != 0;
             qb = &(*qb)->_Next)
          if (size <= (*qb)->_Size)
            return (qb);
      }
      else
      { /* resume where we left off */
        for (; *qb != 0; qb = &(*qb)->_Next)
          if (size <= (*qb)->_Size)
            return (qb);
        q = *_Aldata._Plast;
        for (qb = &_Aldata._Head; *qb != q;
             qb = &(*qb)->_Next)
          if (size <= (*qb)->_Size)
            return (qb);
      }
      { /* try to buy more space */
        psize_t bs;
        
        for (bs = _Size_block; ; bs >>= 1)
        { /* try larger blocks first */
          if (bs < size)
            bs = size;
          if ((q = (_Cell *)_Getmem(bs)) != 0)
            break;
          else if (bs == size)
            return (0); /* no storage */
        }
        /* got storage: add to heap and retry */
        q->_Size = bs;
        _UPD_Altab(q->_Size, q->_Size, 0); /* heap=alloc+free */
        PortFree((char *)q + CELL_OFF);
      }
    }
  }
  
  
  void *(PortMalloc)(psize_t size_arg)
  { /* allocate a data object on the heap */
    _Cell *q, **qb;
#ifdef SPEEDUP
    int qbsBin;
    int qbNextBin;
#endif /* SPEEDUP */
    psize_t size;
    
    passert(pmallocInitted);
    
    size = (size_arg + (CELL_OFF + M_MASK)) & ~M_MASK;
    
    _OK_Altab(&_Aldata);
    if (size <= size_arg)
      return (0); /* size_arg too large */
    if (size < SIZE_CELL) /* round up size */
      size = SIZE_CELL;
    if ((qb = findmem(size)) == 0)
      return (0);
    q = *qb;
    if (q->_Size - SIZE_CELL < size)
    {
      /* use entire cell (there's not enough space to carve out a new cell from this one) */
#ifdef SPEEDUP
      /* remove *qb cell from free list.
       * careful : the Next pointer may be in a different bin.
       */
      qbsBin = GET_MEM_BIN(*qb);
      
      /* Check whether the cell is at the end of the 'free' linked-list */
      if (0 != ((*qb)->_Next))
      {
        /* The cell is not at the end of the free linked-list; find out which bin the next free cell is in */
        qbNextBin = GET_MEM_BIN((*qb)->_Next);
        
        if (qbsBin == qbNextBin)
        {
          if (binsFirstFreeCell[qbsBin] == *qb)
          {
            /* The allocated cell was the first free cell in the bin; update the first free cell
               pointer to point to the next free cell */
            binsFirstFreeCell[qbsBin] = (*qb)->_Next;
          }
        }
        else
        {
          if (binsFirstFreeCell[qbsBin] == *qb)
          {
            /* The allocated cell was the only free cell in the bin; update the first free cell
               pointer to point to NULL */
            
            binsFirstFreeCell[qbsBin] = 0;
          }
        }
      }
      else
      {
        /* Cell is at the end of the 'free' linked-list */
        if (binsFirstFreeCell[qbsBin] == *qb)
        {
          /* The allocated cell was the first free cell in the bin; there are no following free cells
             so set the first free cell pointer to NULL */
          binsFirstFreeCell[qbsBin] = 0;
        }
      }
#endif /* SPEEDUP */
      *qb = q->_Next;
    }
    else
    { /* peel off a residual cell */
      *qb = (_Cell *)((char *)q + size);
      (*qb)->_Next = q->_Next;
      (*qb)->_Size = q->_Size - size;
      q->_Size = size;
#ifdef SPEEDUP
      /* remove q from free list, and add *qb to free list.
       * Do this as two separate steps because they may be in 2 different bins.
       */
      /* remove q from free list */
      if (binsFirstFreeCell[GET_MEM_BIN(q)] == q)
        binsFirstFreeCell[GET_MEM_BIN(q)] = 0;
      /* now add *qb to its bin's free list if it's the first */
      qbsBin = GET_MEM_BIN(*qb);
      if ((binsFirstFreeCell[qbsBin] == 0) || (*qb < binsFirstFreeCell[qbsBin]))
        binsFirstFreeCell[qbsBin] = *qb;
#endif /* SPEEDUP */
    }
    _Aldata._Plast = qb; /* resume scan here */
    _UPD_Altab(0, q->_Size, -q->_Size); /* heap=alloc+free */
    _OK_Altab(&_Aldata);
    return ((char *)q + CELL_OFF);
  }
  _STD_END
  
  
  /* free function */
  _STD_BEGIN
  
  void(PortFree)(void *ptr)
  { /* free an allocated data object */
    register _Cell *q;
    register psize_t size;
#ifdef SPEEDUP
    int binNum;
    int binIndex;
    int qNextBin;
    int qNextNextBin;
#endif /* SPEEDUP */
    static int portFreeCount = 0;
    portFreeCount++;
    
    passert(pmallocInitted);
    
    _OK_Altab(&_Aldata);
    if (ptr == 0)
      return;
    q = (_Cell *)((char *)ptr - CELL_OFF);
    size = q->_Size;
#ifdef SPEEDUP
    binNum = GET_MEM_BIN(q);
#endif /* SPEEDUP */
    if (size < SIZE_CELL || (size & M_MASK) != 0)
      return; /* erroneous call, bad count */
    if (_Aldata._Head == 0
        || _PTR_NORM(q) < _PTR_NORM(_Aldata._Head))
    { /* insert at head of list */
      q->_Next = _Aldata._Head;
      _Aldata._Head = q;
#ifdef SPEEDUP
      /* always the start of a bin */
      binsFirstFreeCell[binNum] = q;
#endif /* SPEEDUP */
    }
    else
    { /* scan for insertion point */
      register _Cell *qp = _Aldata._Head;
      register char *qpp;
      register _Cell *nextCell;
#if !defined SPEEDUP || defined SPEEDUP_COMPARE
      _Cell *savedQp;
      
      /* this search loop is where all the time is spent */
      while ((nextCell = qp->_Next) != 0
             && _PTR_NORM(nextCell) < _PTR_NORM(q))
        qp = qp->_Next;
      savedQp = qp;
#endif /* SPEEDUP */
      
#ifdef SPEEDUP
      /* this is where the SPEEDUP code is sped up : start with the bin's first free cell */
      _Cell *firstFreeInBin = binsFirstFreeCell[binNum];
      if ((firstFreeInBin != 0) && (q > firstFreeInBin))
      {
        qp = firstFreeInBin;
        while ((nextCell = qp->_Next) != 0
               && _PTR_NORM(nextCell) < _PTR_NORM(q))
        {
          qp = qp->_Next;
        }
      }
      else
      {
        /* go back to the previous non-zero bin */
        qp = NULL;  /* for diagnostics */
        for (binIndex = binNum; binIndex >= 0; binIndex--)
        {
          if ((binsFirstFreeCell[binIndex] != 0) && (q > binsFirstFreeCell[binIndex]))
          {
            qp = binsFirstFreeCell[binIndex];
            break;
          }
        }
        /* this code for diagnostic purposes to see how often it happens. otherwise,
         * qp could have been set to _Aldata._Head prior to the binIndex loop above.
         */
        if (qp == NULL)
        {
          qp = _Aldata._Head;
        }
        
        /* find the free cell location */
        while ((nextCell = qp->_Next) != 0
               && _PTR_NORM(nextCell) < _PTR_NORM(q))
          qp = qp->_Next;
      }
      
#ifdef SPEEDUP_COMPARE
      if (qp != savedQp)
        printf("oops \n");
#endif /* SPEEDUP_COMPARE */
#endif /* SPEEDUP */
        
#if !defined SPEEDUP || defined SPEEDUP_COMPARE
      qp = savedQp;
#endif /* SPEEDUP */
      
      qpp = (char *)qp + qp->_Size;
      if (_PTR_NORM((char *)q) < _PTR_NORM(qpp))
        return; /* erroneous call, overlaps qp */
      else if (_PTR_NORM(qpp) == _PTR_NORM((char *)q))
      { /* merge qp and q (merge with prior cell) */
        /* nothing to do to bin's free list here */
        qp->_Size += q->_Size;
        q = qp;
      }
      else if (qp->_Next != 0 && _PTR_NORM((char *)qp->_Next)
               < _PTR_NORM((char *)q + q->_Size))
        return; /* erroneous call, overlaps qp->_Next */
      else
      { /* splice q after qp */
#ifdef SPEEDUP
        /* add 1 entry here - this could change first entry in q's bin */
        _Cell *firstFree = binsFirstFreeCell[binNum];
        if ((firstFree == 0) || (q < firstFree))
          binsFirstFreeCell[binNum] = q;
#endif /* SPEEDUP */
        q->_Next = qp->_Next;
        qp->_Next = q;
      }
    }
    if (q->_Next != 0 && _PTR_NORM((char *)q + q->_Size)
        == _PTR_NORM((char *)q->_Next))
    { /* merge q and q->_Next (merge with latter cell) */
#ifdef SPEEDUP
      /* lose 1 cell here - this could change first entry in bin.
       * if q->_Next was first in bin, now it's its Next.
       * careful : watch for next being in a different bin.
       */
      qNextBin = GET_MEM_BIN(q->_Next);
      
      if (binsFirstFreeCell[qNextBin] == q->_Next)
      {
        /* The q->_Next cell is the first free cell in its bin; set the first free cell
           pointer to NULL for now; if there is another free cell in the same bin then
           the first free cell pointer will be updated in next 'if' code block */
        binsFirstFreeCell[qNextBin] = 0;
        
        /* If there is another free cell after q->_Next and it's in the same bin then
           update the first free cell pointer if necessary */
        if (0 != (q->_Next->_Next))
        {
          qNextNextBin = GET_MEM_BIN(q->_Next->_Next);
          
          /* The first free cell pointer for q->_Next->_Next's bin can only be set to 0
             by the code above; if it is 0 then q->_Next->_Next must be the first free
             cell in the bin */
          if (0 == binsFirstFreeCell[qNextNextBin])
          {
            binsFirstFreeCell[qNextNextBin] = q->_Next->_Next;
          }
        }
      }
#endif /* SPEEDUP */
      _Aldata._Plast = 0; /* deoptimize for safety */
      q->_Size += q->_Next->_Size;
      q->_Next = q->_Next->_Next;
    }
    _UPD_Altab(0, -size, size); /* heap=alloc+free */
    _OK_Altab(&_Aldata);
    /* every successful free "falls off" here */
  }
  _STD_END
  
#ifdef __cplusplus
} /* end extern "C" */
#endif

#endif /* PORTABLE_DINKUM_MEM_MGR */