blob: eaf8007aa2c273d33f3910b0ca637e5c01334572 [file] [log] [blame]
/*---------------------------------------------------------------------------*
* 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 */