/*---------------------------------------------------------------------------*
 *  pmemory_ext.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.                                           *
 *                                                                           *
 *---------------------------------------------------------------------------*/



#include "pmemory.h"
#include "ptrd.h"
#include "pmutex.h"
#include "passert.h"
#include "pmemory_ext.h"
#include "pmalloc.h"

#ifdef __cplusplus
extern "C"
{
#endif

#if defined(USE_THREAD) && defined(USE_DINKUM_LIB_DIRECT)
  static MUTEX memextMutex;
#endif
  
#ifdef RTXC
  void* operator new(size_t size)
  {
    return (PortNew(size));
  }
  void  operator delete(void* ptr)
  {
    PortDelete(ptr);
  }
#endif
  
#if defined(PORTABLE_DINKUM_MEM_MGR) || defined(PORTABLE_FIXED_SIZE_MEM_BLOCK_SCHEME)
  
  /* to assist with leak checking */
static int portNewCount = 0;
static int portDeleteCount = 0;

  /* enable writing and checking of guard words if debugging is enabled */
#ifdef _DEBUG
  /* crash on Xanavi's board with this option on, do not know why */
  /* #define DBG_GUARD_WORDS */
#endif /* _DEBUG */
  
  /* ************************************************************************************
   * PORTABLE_PSOS_BLOCK_SCHEME_MEM_MGR || PORTABLE_DINKUM_MEM_MGR || PORTABLE_FIXED_SIZE_MEM_BLOCK_SCHEME
   * ************************************************************************************/
  
  /* data ******************************************************************************/
  
  static BOOL  gMemoryInitted = FALSE; /* TODO: Temporary fix to PortTerm failure */
  
#define MEM_MGR_GetPoolSize()     PortMallocGetPoolSize()
#define MEM_MGR_SetPoolSize(sizeInBytes)  PortMallocSetPoolSize(sizeInBytes)
#define MEM_MGR_Init()       PortMallocInit()
#define MEM_MGR_Term()              PortMallocTerm()
#define MEM_MGR_Allocate(sizeInBytes)   PortMalloc(sizeInBytes)
#define MEM_MGR_Free(objectPtr)     PortFree(objectPtr)
#define MEM_MGR_Dump()
#define MEM_MGR_GetMaxMemUsed()     PortMallocGetMaxMemUsed()
  
  /* guard word data ********************************************************/
  
#ifdef DBG_GUARD_WORDS
#define GUARD_BEGIN  0xbbbbbbbb
#define GUARD_END    0xeeeeeeee
  
#define GUARD_OFF_REQ_SIZE   0
#define GUARD_OFF_START    sizeof(unsigned int)
#define GUARD_OFF_PTR    (sizeof(unsigned int) + sizeof(unsigned int))
#define GUARD_EXTRA     (sizeof(unsigned int) + sizeof(unsigned int) + sizeof(unsigned int))
#define GUARD_OFF_END(allocSize)    ((allocSize) - sizeof(unsigned int))
#define GUARD_ALLOC_SIZE(reqSize)   ((reqSize)+GUARD_EXTRA)
  
#define GUARD_PTR_FIELD(ptr,off) (unsigned int *)((char *)(ptr) + (off))
#define GUARD_ALLOC_PTR(ptr)  (void*) ((char *)(ptr) - GUARD_OFF_PTR)
#endif
  
  /* scan guard words data **************************************************/
  
  /* maintain a static list of allocated blocks (didn't want to perform any dynamic allocation).
   * This list can be scanned by PortMemScan() to determine if any allocated blocks
   * have overwritten their guard words.
   * Calling PortDelete() will check guard words upon de-allocation, but many
   * allocated blocks are only freed at program termination, which sometimes doesn't happen.
   *
   * This software is enabled separately with DBG_SCAN_GUARD_WORDS, because the performance
   * overhead is severe.
   */
#ifdef DBG_SCAN_GUARD_WORDS
#define MAX_ALLOCATED_BLOCKS  80000
  static void *allocArray[MAX_ALLOCATED_BLOCKS+1];
  static int allocArrayCount = 0;
  
  void AddToAllocList(void *memPtr);
  void RemoveFromAllocList(void *memPtr);
  
#define ADD_TO_ALLOC_LIST(ptr)   AddToAllocList(ptr)
#define REMOVE_FROM_ALLOC_LIST(ptr)  RemoveFromAllocList(ptr)
  
#else
#define ADD_TO_ALLOC_LIST(ptr)
#define REMOVE_FROM_ALLOC_LIST(ptr)
#endif
  
  /* Guard Functions ********************************************************/
  
#ifdef DBG_SCAN_GUARD_WORDS
  /* AddToAllocList() : maintain an array of allocated blocks that can be
   * used by PortMemScan() to check for overwritten guard words.
   */
  void AddToAllocList(void *memPtr)
  {
    allocArray[allocArrayCount] = memPtr;
    allocArrayCount++;
    if (allocArrayCount >= MAX_ALLOCATED_BLOCKS)
    {
      char buf[256];
      sprintf(buf, "AddToAllocList ERROR : MAX_ALLOCATED_BLOCKS is too small (%d)", allocArrayCount);
      PORT_INTERNAL_ERROR(buf);
    }
  }
  
  /* RemoveFromAllocList() : maintain an array of allocated blocks that can be
   * used by PortMemScan() to check for overwritten guard words.
   */
  void RemoveFromAllocList(void *memPtr)
  {
    int i;               /* loop index */
    int j;               /* loop index */
    int inList = FALSE;  /* TRUE when found in list */
    
    for (i = 0; i < allocArrayCount; i++)
    {
      if (allocArray[i] == memPtr)
      {
        inList = TRUE;
        break;
      }
    }
    PORT_ASSERT(inList == TRUE);  /* MUST be in list */
    /* remove by sliding down all following entries */
    for (j = i + 1; j < allocArrayCount; j++)
      allocArray[j-1] = allocArray[j];
    allocArrayCount--;
    allocArray[allocArrayCount] = NULL; /* clear out end of list */
  }
  
  /* PortMemScan() : scan the array of allocated blocks, confirming that no
   * allocated block has overwritten its guard words.
   */
  void PortMemScan(void)
  {
    int          i;
    
    PortCriticalSectionEnter(&PortMemoryCriticalSection);
    
    /* scan the allocated memory list */
    for (i = 0; i < allocArrayCount; i++)
    {
      /* verify that guard words have not been corrupted */
      void   *memPtr   = allocArray[i];
      void         *allocPtr      = GUARD_ALLOC_PTR(memPtr);
      unsigned int *requestedSizePtr  = GUARD_PTR_FIELD(allocPtr, GUARD_OFF_REQ_SIZE);
      unsigned int *guardStartPtr     = GUARD_PTR_FIELD(allocPtr, GUARD_OFF_START);
      unsigned int *guardEndPtr       = GUARD_PTR_FIELD(allocPtr, GUARD_OFF_END(GUARD_ALLOC_SIZE(*requestedSizePtr)));
      
      if ((*guardStartPtr) != GUARD_BEGIN)
      {
        PLogError("PortMemScan : corrupted start guard from block 0x%08x \n", (int)memPtr);
      }
      if ((*guardEndPtr)   != GUARD_END)
      {
        PLogError("PortMemScan : corrupted end guard from block 0x%08x \n", (int)memPtr);
      }
    }
    
    PortCriticalSectionLeave(&PortMemoryCriticalSection);
  }
#endif /* DBG_SCAN_GUARD_WORDS */
  
  /* Port Memory Functions ******************************************************/
  
  /* PortMemGetPoolSize() : return size of portable memory pool, or 0 if
   * unknown.
   */
  int  PortMemGetPoolSize(void)
  {
    return MEM_MGR_GetPoolSize();
  }
  
  /* PortMemSetPoolSize() : set size of portable memory pool on PSOS.
   * This must be called before PortMemoryInit(), which is called by PortInit().
   */
  void PortMemSetPoolSize(size_t sizeInBytes)
  {
    MEM_MGR_SetPoolSize(sizeInBytes);
  }
  
  /* PortMemoryInit() :
   */
  
  int  PortMemoryInit(void)
  {
#if defined(USE_THREAD) && defined(USE_DINKUM_LIB_DIRECT)
    if (createMutex(&memextMutex) == ESR_SUCCESS)
#endif
    {
      if (!gMemoryInitted)
      {
        MEM_MGR_Init();
        gMemoryInitted = TRUE;
      }
    }
    
    return gMemoryInitted;
  }
  
  /* PortMemoryTerm() :
   */
  
  void  PortMemoryTerm(void)
  {
    /* TODO: MEM_PSOS_BLOCK_SCHEME
     * Figure out why free memory causes rn#0 is get messed up! */
    MEM_MGR_Term();
#if defined(USE_THREAD) && defined(USE_DINKUM_LIB_DIRECT)
    deleteMutex(&memextMutex);
#endif
    gMemoryInitted = FALSE;
  }
  
  /* PortNew() :
   */
  
  void* PortNew(size_t sizeInBytes)
  {
    if (gMemoryInitted)
    {
      void *pMemory = NULL;
      
#if defined(USE_THREAD) && defined(USE_DINKUM_LIB_DIRECT)
      lockMutex(&memextMutex);
#endif
      portNewCount++;
      
#ifdef DBG_GUARD_WORDS
      sizeInBytes += GUARD_EXTRA; /* space for: requestedSize,guardStart,guardEnd */
#endif
      
      pMemory = MEM_MGR_Allocate(sizeInBytes);
      
#ifdef DBG_GUARD_WORDS
      if (NULL != pMemory)
      {
        /* at the beginning of the buffer, store the requested size and a guard word.
         * Store another guard word at the end of the buffer.
         */
        /* set guard words at either end of allocated buffer; will be checked at delete time */
        unsigned int * requestedSizePtr  = GUARD_PTR_FIELD(pMemory, GUARD_OFF_REQ_SIZE);
        unsigned int * guardStartPtr     = GUARD_PTR_FIELD(pMemory, GUARD_OFF_START);
        unsigned int * guardEndPtr       = GUARD_PTR_FIELD(pMemory, GUARD_OFF_END(sizeInBytes));
        
        *requestedSizePtr = sizeInBytes - GUARD_EXTRA;
        *guardStartPtr    = GUARD_BEGIN;
        *guardEndPtr      = GUARD_END;
        pMemory     = (void *) GUARD_PTR_FIELD(pMemory, GUARD_OFF_PTR);
        ADD_TO_ALLOC_LIST(pMemory);
      }
#endif /* DBG_GUARD_WORDS */
#if defined(USE_THREAD) && defined(USE_DINKUM_LIB_DIRECT)
      unlockMutex(&memextMutex);
#endif
      return pMemory;
    }
#ifdef PSOSIM
    /* PSOSIM's license manager calls new() before PSOS is running */
    else
    {
      return(malloc(sizeInBytes));
    }
#else  /* PSOSIM */
    /* Memory allocator not initialized when request for memory was made */
    passert(FALSE && "Call PortInit() before calling any portable functions\r\n");
    return NULL;
#endif /* PSOSIM */
  }
  
  void PortDelete(void* objectPtr)
  {
#if defined(USE_THREAD) && defined(USE_DINKUM_LIB_DIRECT)
    lockMutex(&memextMutex);
#endif
    portDeleteCount++;
    
#ifdef DBG_GUARD_WORDS
    {
      /* verify that guard words have not been corrupted */
      void *allocPtr     = GUARD_ALLOC_PTR(objectPtr);
      unsigned int *requestedSizePtr  = GUARD_PTR_FIELD(allocPtr, GUARD_OFF_REQ_SIZE);
      unsigned int *guardStartPtr     = GUARD_PTR_FIELD(allocPtr, GUARD_OFF_START);
      unsigned int *guardEndPtr       = GUARD_PTR_FIELD(allocPtr, GUARD_OFF_END(GUARD_ALLOC_SIZE(*requestedSizePtr)));
      
      passert((*guardStartPtr) == GUARD_BEGIN);
      passert((*guardEndPtr)   == GUARD_END);
      REMOVE_FROM_ALLOC_LIST(allocPtr);
      objectPtr = allocPtr;
    }
#endif
    
    MEM_MGR_Free(objectPtr);
#if defined(USE_THREAD) && defined(USE_DINKUM_LIB_DIRECT)
    unlockMutex(&memextMutex);
#endif
  }
  
  void PortMemTrackDump(void)
  {
    MEM_MGR_Dump();
  }
  
  /* PortGetMaxMemUsed() : return the maximum real memory allocated.
   * There is another function of the same name in pmalloc.c, for tracking
   * non-psos block memory. It uses #ifndef MEM_PSOS_BLOCK_SCHEME to enable.
   */
  int PortGetMaxMemUsed(void)
  {
    return MEM_MGR_GetMaxMemUsed();
  }
  
  /* PortMemCntReset() : reset the New/Delete count.
   * This is useful for checking that each new has a corresponding delete once
   * the system gets into a steady state.
   */
  void PortMemCntReset()
  {
    portNewCount = 0;
    portDeleteCount = 0;
  }
  
  
  /* PortMemGetCount() : return the accumulated new & delete counts */
  void PortMemGetCount(int *newCount, int *deleteCount)
  {
    *newCount    = portNewCount;
    *deleteCount = portDeleteCount;
  }
  
#endif /* (==PORTABLE_PSOS_BLOCK_SCHEME_MEM_MGR) || (==PORTABLE_DINKUM_MEM_MGR) || (==PORTABLE_FIXED_SIZE_MEM_BLOCK_SCHEME) */
  
#ifdef __cplusplus
}
#endif