1/*---------------------------------------------------------------------------*
2 *  pmalloc.c  *
3 *                                                                           *
4 *  Copyright 2007, 2008 Nuance Communciations, Inc.                               *
5 *                                                                           *
6 *  Licensed under the Apache License, Version 2.0 (the 'License');          *
7 *  you may not use this file except in compliance with the License.         *
8 *                                                                           *
9 *  You may obtain a copy of the License at                                  *
10 *      http://www.apache.org/licenses/LICENSE-2.0                           *
11 *                                                                           *
12 *  Unless required by applicable law or agreed to in writing, software      *
13 *  distributed under the License is distributed on an 'AS IS' BASIS,        *
14 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
15 *  See the License for the specific language governing permissions and      *
16 *  limitations under the License.                                           *
17 *                                                                           *
18 *---------------------------------------------------------------------------*/
19
20
21
22
23/* this source file is only used when PORTABLE_DINKUM_MEM_MGR is defined
24 */
25#ifdef PORTABLE_DINKUM_MEM_MGR
26
27#include <stdlib.h>
28#include <string.h> /* for memset */
29#include "pmalloc.h"
30#include "passert.h"
31#include "ptypes.h"
32#include "plog.h"
33
34#undef malloc
35#undef calloc
36#undef realloc
37#undef free
38
39#ifdef __cplusplus
40extern "C"
41{
42#endif
43
44  /*
45   * There are two controlling options within this scheme:
46   *
47   * STATIC_MEMORY_POOL: When defined, there is a static array from which memory is
48   * allocated.  The size of this array is defined at compile time.  When undefined
49   * (the default), the memory is allocated via malloc().  This code works under PSOS and
50   * PSOSIM, but has not been tested anywhere else (4March02).
51   * VOYAGER_KERNEL_MEMORY: When defined for the Voyager platform, it is similar to the
52   * non-static memory pool, but the memory buffer is pre-defined, and is simply pointed
53   * at by the pmalloc initializer.
54   * RTXC_PARTITION_MEMORY: When defined for the RTXC operating system, uses a static kernel
55   * partition resource for the memory chunk.
56   * VOYAGER_KERNEL_MEMORY and RTXC_PARTITION_MEMORY are mutually exclusive and take precedence
57   * over STATIC_MEMORY.
58   *
59
60   * the standard off-the-shelf Dinkumware software is pretty slow, primarily due to
61   * scanning the free-memory linked list in PortFree(). If SPEEDUP is defined, then
62   * split the memory pool into imaginary 'bins', and keep track of the first free list
63   * entry in each bin. Then the linked list lookup can be MUCH faster, as you can
64   * start very close to the final destination in the linked list.
65   *
66   * (If SPEEDUP_COMPARE is defined, then run BOTH the standard technique and the
67   * speedup technique and compare the results.)
68   */
69
70  /* malloc function */
71  _STD_BEGIN
72
73  /* data *******************************************************************************/
74
75#if defined(PORTABLE_DINKUM_MEM_MGR) || defined(PORTABLE_FIXED_SIZE_MEM_BLOCK_SCHEME)
76  /* Verify that memory pool actually was created, because of the lack of structure, this is accessed externally */
77  ESR_ReturnCode memory_pool_creation_status = ESR_FATAL_ERROR;
78#endif
79
80  /* static data */
81  _Altab  _Aldata  = {0}; /* heap initially empty */
82  psize_t  _Size_block = {SIZE_BLOCK}; /* preferred _Getmem chunk */
83
84  /* Memory pool size */
85#define MEM_SIZE_MB( mbytes )   ((mbytes) * 1024 * 1024 )
86
87#ifndef MEM_SIZE
88  /* If not defined on the command line, use default values. */
89#define MEM_SIZE    MEM_SIZE_MB( 5 )
90#endif
91
92  /* Memory pool initialized */
93  static int pmallocInitted = FALSE;  /* TRUE once initialized */
94
95#ifdef STATIC_MEMORY_POOL
96  /* The memory pool can either be statically allocated or require a one-time system malloc.
97   * For VTB, the system was taking 2 seconds to zero the static memBuffer[] array at
98   * boot time, since it's in the BSS segment. Therefore, for VTB, it is better to allocate
99   * at run time.
100   */
101  static char memBuffer[MEM_SIZE];
102#else
103  static char *memBuffer;
104#endif
105
106  static psize_t memSize = MEM_SIZE;
107
108  /* Memory pool free list */
109  /* partition memory range into 'bins', and keep track of the first
110   * free list entry in each bin. This is to speed up the linked-list search
111   * that takes place when memory is freed.
112   */
113#define BIN_BITS         14   /* smaller number ==> more bins */
114#define BIN_SIZE      16384   /* 2 ^ BIN_BITS */
115
116#define __NUM_MEM_BINS(memPoolSize)  (((memPoolSize)/BIN_SIZE) + 5) /* 5 = extra for roundoff */
117#define GET_MEM_BIN( _ptr_ )   (int)(((unsigned int)_ptr_ - (unsigned int)&memBuffer[0]) >> BIN_BITS)
118
119#define NUM_MEM_BINS  __NUM_MEM_BINS(MEM_SIZE)
120static _Cell				*binsFirstFreeCell[NUM_MEM_BINS+1] = {0};
121  static psize_t    numMemBins;
122
123  /* Memory Pool sbrk/getmem variables */
124
125  static char *__heap_ptr = NULL;
126  static char *__heap_end = NULL;
127  static int  maxMemUsed = 0;
128
129  /* Memory Pool initialization and _GetMem functions ************************************/
130
131#if _USE_EXISTING_SYSTEM_NAMES
132#define _Sbrk sbrk
133#endif
134
135  _STD_BEGIN
136
137  void *_Sbrk(int incr)
138  {
139    char *ret;
140
141    /* Subtract 1 from __heap_ptr so that the left hand side of the comparison evaluates to the address of the
142       last address of the requested memory block */
143    if ((__heap_ptr + incr - 1) > __heap_end) return(void *) - 1;
144
145    ret = __heap_ptr;
146    __heap_ptr += incr;
147    maxMemUsed += incr;
148    return (void *)ret;
149  }
150
151  void *_Getmem(psize_t size)
152  { /* allocate raw storage */
153    void *p;
154    int isize = size;
155
156    return (isize <= 0 || (p = _Sbrk(isize)) == (void *) - 1
157            ? 0 : p);
158  }
159  _STD_END
160
161  /* PortMallocInit() : initialize memory pool. There is no initialization needed for
162   * a static memory pool. Otherwise, perform a one-time malloc from the OS.
163   */
164  void PortMallocInit(void)
165  {
166#if defined STATIC_MEMORY_POOL
167    memSize = MEM_SIZE;
168#else
169    /* TODO: is malloc of binsFirstFreeCell safe under PSOS in all conditions ? */
170    memBuffer    = (char *)malloc(memSize);
171#if defined(PORTABLE_DINKUM_MEM_MGR) || defined(PORTABLE_FIXED_SIZE_MEM_BLOCK_SCHEME)
172    if (memBuffer != NULL) /* For external access, check comment at top */
173      memory_pool_creation_status = ESR_SUCCESS;
174#endif
175    numMemBins    = __NUM_MEM_BINS(memSize);
176#endif /* #ifdef VOYAGER_KERNEL_MEMORY */
177
178    passert(memBuffer != NULL);
179    passert(binsFirstFreeCell != NULL);
180
181    __heap_ptr = &memBuffer[0];
182    __heap_end = &memBuffer[memSize-1];
183
184    /* set initted flag so we only do this once */
185    pmallocInitted = TRUE;
186    maxMemUsed = 0;
187
188    memset(&_Aldata, 0, sizeof(_Altab));
189
190    memset(binsFirstFreeCell, 0, sizeof(_Cell*)*(NUM_MEM_BINS + 1));
191  }
192
193  void PortMallocTerm(void)
194  {
195#ifndef STATIC_MEMORY_POOL
196    memSize = 0;
197    free(memBuffer);
198    memBuffer = NULL;
199#if defined(PORTABLE_DINKUM_MEM_MGR) || defined(PORTABLE_FIXED_SIZE_MEM_BLOCK_SCHEME)
200    memory_pool_creation_status = ESR_FATAL_ERROR; /* For external access, check comment at top */
201#endif
202#endif
203    pmallocInitted = FALSE;
204  }
205
206  /* PortGetMaxMemUsed() : return the maximum real memory allocated.
207   * There is another function of the same name in pmemory.cpp, for tracking
208   * psos block memory. It uses #ifdef MEM_PSOS_BLOCK_SCHEME to enable.
209   */
210  int PortMallocGetMaxMemUsed(void)
211  {
212    return maxMemUsed;
213  }
214
215  /* PortMallocSetPoolSize( psize_t size ) : define size of memory pool.
216   */
217  void PortMallocSetPoolSize(psize_t size)
218  {
219#if !defined(STATIC_MEMORY_POOL) && !defined(VOYAGER_KERNEL_MEMORY) && !defined(RTXC_PARTITION_MEMORY)
220    if (!pmallocInitted)
221    {
222      memSize = size;
223    }
224#else
225    (void)size;
226#endif
227  }
228
229  /* PortMallocGetPoolSize() : return size of memory pool.
230   */
231  psize_t PortMallocGetPoolSize(void)
232  {
233#if defined STATIC_MEMORY_POOL
234    return MEM_SIZE;
235#else
236    return memSize;
237#endif
238  }
239
240  /* debug *******************************************************************************/
241
242  /* xalloc.h internal header - debugging components */
243#if DEBUG
244
245  int _OK_Cell(_Cell *p)
246  {
247    passert(SIZE_CELL <= p->_Size);
248    return 1;
249  }
250
251  typedef struct _DB_Altab
252  {
253    psize_t total_heap;
254    psize_t total_alloc;
255    psize_t total_free;
256  }
257  _DB_Altab;
258
259  _DB_Altab _DB_Altab_object = {0};
260
261  void _UPD_Altab(psize_t d_heap, psize_t d_alloc, psize_t d_free)
262  {
263    _DB_Altab *pd = &_DB_Altab_object;
264    pd->total_heap += d_heap;
265    pd->total_alloc += d_alloc;
266    pd->total_free += d_free;
267  }
268
269  int _OK_Altab(_Altab *p)
270  {
271    _DB_Altab *pd = &_DB_Altab_object;
272    _Cell *q;
273    psize_t total_free = 0;
274    if (p->_Head == 0)
275      return 1;
276    for (q = p->_Head; q != 0; q = q->_Next)
277    {
278      total_free += q->_Size;
279      _OK_Cell(q);
280      if (q->_Next != 0)
281      {
282        passert(_PTR_NORM((char *)q + q->_Size) <=
283                _PTR_NORM((char *)q->_Next));
284        passert(_PTR_NORM(q) < _PTR_NORM(q->_Next));
285      }
286    }
287    passert(pd->total_heap == pd->total_alloc + pd->total_free);
288    passert(total_free == pd->total_free);
289    return 1;
290  }
291#endif /* DEBUG */
292
293  /* allocation functions ***************************************************************/
294
295  static _Cell **findmem(psize_t size)
296  { /* find storage */
297    _Cell *q, **qb;
298
299    for (; ;)
300    { /* check freed space first */
301      if ((qb = _Aldata._Plast) == 0)
302      { /* take it from the top */
303        for (qb = &_Aldata._Head; *qb != 0;
304             qb = &(*qb)->_Next)
305          if (size <= (*qb)->_Size)
306            return (qb);
307      }
308      else
309      { /* resume where we left off */
310        for (; *qb != 0; qb = &(*qb)->_Next)
311          if (size <= (*qb)->_Size)
312            return (qb);
313        q = *_Aldata._Plast;
314        for (qb = &_Aldata._Head; *qb != q;
315             qb = &(*qb)->_Next)
316          if (size <= (*qb)->_Size)
317            return (qb);
318      }
319      { /* try to buy more space */
320        psize_t bs;
321
322        for (bs = _Size_block; ; bs >>= 1)
323        { /* try larger blocks first */
324          if (bs < size)
325            bs = size;
326          if ((q = (_Cell *)_Getmem(bs)) != 0)
327            break;
328          else if (bs == size)
329            return (0); /* no storage */
330        }
331        /* got storage: add to heap and retry */
332        q->_Size = bs;
333        _UPD_Altab(q->_Size, q->_Size, 0); /* heap=alloc+free */
334        PortFree((char *)q + CELL_OFF);
335      }
336    }
337  }
338
339
340  void *(PortMalloc)(psize_t size_arg)
341  { /* allocate a data object on the heap */
342    _Cell *q, **qb;
343#ifdef SPEEDUP
344    int qbsBin;
345    int qbNextBin;
346#endif /* SPEEDUP */
347    psize_t size;
348
349    passert(pmallocInitted);
350
351    size = (size_arg + (CELL_OFF + M_MASK)) & ~M_MASK;
352
353    _OK_Altab(&_Aldata);
354    if (size <= size_arg)
355      return (0); /* size_arg too large */
356    if (size < SIZE_CELL) /* round up size */
357      size = SIZE_CELL;
358    if ((qb = findmem(size)) == 0)
359      return (0);
360    q = *qb;
361    if (q->_Size - SIZE_CELL < size)
362    {
363      /* use entire cell (there's not enough space to carve out a new cell from this one) */
364#ifdef SPEEDUP
365      /* remove *qb cell from free list.
366       * careful : the Next pointer may be in a different bin.
367       */
368      qbsBin = GET_MEM_BIN(*qb);
369
370      /* Check whether the cell is at the end of the 'free' linked-list */
371      if (0 != ((*qb)->_Next))
372      {
373        /* The cell is not at the end of the free linked-list; find out which bin the next free cell is in */
374        qbNextBin = GET_MEM_BIN((*qb)->_Next);
375
376        if (qbsBin == qbNextBin)
377        {
378          if (binsFirstFreeCell[qbsBin] == *qb)
379          {
380            /* The allocated cell was the first free cell in the bin; update the first free cell
381               pointer to point to the next free cell */
382            binsFirstFreeCell[qbsBin] = (*qb)->_Next;
383          }
384        }
385        else
386        {
387          if (binsFirstFreeCell[qbsBin] == *qb)
388          {
389            /* The allocated cell was the only free cell in the bin; update the first free cell
390               pointer to point to NULL */
391
392            binsFirstFreeCell[qbsBin] = 0;
393          }
394        }
395      }
396      else
397      {
398        /* Cell is at the end of the 'free' linked-list */
399        if (binsFirstFreeCell[qbsBin] == *qb)
400        {
401          /* The allocated cell was the first free cell in the bin; there are no following free cells
402             so set the first free cell pointer to NULL */
403          binsFirstFreeCell[qbsBin] = 0;
404        }
405      }
406#endif /* SPEEDUP */
407      *qb = q->_Next;
408    }
409    else
410    { /* peel off a residual cell */
411      *qb = (_Cell *)((char *)q + size);
412      (*qb)->_Next = q->_Next;
413      (*qb)->_Size = q->_Size - size;
414      q->_Size = size;
415#ifdef SPEEDUP
416      /* remove q from free list, and add *qb to free list.
417       * Do this as two separate steps because they may be in 2 different bins.
418       */
419      /* remove q from free list */
420      if (binsFirstFreeCell[GET_MEM_BIN(q)] == q)
421        binsFirstFreeCell[GET_MEM_BIN(q)] = 0;
422      /* now add *qb to its bin's free list if it's the first */
423      qbsBin = GET_MEM_BIN(*qb);
424      if ((binsFirstFreeCell[qbsBin] == 0) || (*qb < binsFirstFreeCell[qbsBin]))
425        binsFirstFreeCell[qbsBin] = *qb;
426#endif /* SPEEDUP */
427    }
428    _Aldata._Plast = qb; /* resume scan here */
429    _UPD_Altab(0, q->_Size, -q->_Size); /* heap=alloc+free */
430    _OK_Altab(&_Aldata);
431    return ((char *)q + CELL_OFF);
432  }
433  _STD_END
434
435
436  /* free function */
437  _STD_BEGIN
438
439  void(PortFree)(void *ptr)
440  { /* free an allocated data object */
441    register _Cell *q;
442    register psize_t size;
443#ifdef SPEEDUP
444    int binNum;
445    int binIndex;
446    int qNextBin;
447    int qNextNextBin;
448#endif /* SPEEDUP */
449    static int portFreeCount = 0;
450    portFreeCount++;
451
452    passert(pmallocInitted);
453
454    _OK_Altab(&_Aldata);
455    if (ptr == 0)
456      return;
457    q = (_Cell *)((char *)ptr - CELL_OFF);
458    size = q->_Size;
459#ifdef SPEEDUP
460    binNum = GET_MEM_BIN(q);
461#endif /* SPEEDUP */
462    if (size < SIZE_CELL || (size & M_MASK) != 0)
463      return; /* erroneous call, bad count */
464    if (_Aldata._Head == 0
465        || _PTR_NORM(q) < _PTR_NORM(_Aldata._Head))
466    { /* insert at head of list */
467      q->_Next = _Aldata._Head;
468      _Aldata._Head = q;
469#ifdef SPEEDUP
470      /* always the start of a bin */
471      binsFirstFreeCell[binNum] = q;
472#endif /* SPEEDUP */
473    }
474    else
475    { /* scan for insertion point */
476      register _Cell *qp = _Aldata._Head;
477      register char *qpp;
478      register _Cell *nextCell;
479#if !defined SPEEDUP || defined SPEEDUP_COMPARE
480      _Cell *savedQp;
481
482      /* this search loop is where all the time is spent */
483      while ((nextCell = qp->_Next) != 0
484             && _PTR_NORM(nextCell) < _PTR_NORM(q))
485        qp = qp->_Next;
486      savedQp = qp;
487#endif /* SPEEDUP */
488
489#ifdef SPEEDUP
490      /* this is where the SPEEDUP code is sped up : start with the bin's first free cell */
491      _Cell *firstFreeInBin = binsFirstFreeCell[binNum];
492      if ((firstFreeInBin != 0) && (q > firstFreeInBin))
493      {
494        qp = firstFreeInBin;
495        while ((nextCell = qp->_Next) != 0
496               && _PTR_NORM(nextCell) < _PTR_NORM(q))
497        {
498          qp = qp->_Next;
499        }
500      }
501      else
502      {
503        /* go back to the previous non-zero bin */
504        qp = NULL;  /* for diagnostics */
505        for (binIndex = binNum; binIndex >= 0; binIndex--)
506        {
507          if ((binsFirstFreeCell[binIndex] != 0) && (q > binsFirstFreeCell[binIndex]))
508          {
509            qp = binsFirstFreeCell[binIndex];
510            break;
511          }
512        }
513        /* this code for diagnostic purposes to see how often it happens. otherwise,
514         * qp could have been set to _Aldata._Head prior to the binIndex loop above.
515         */
516        if (qp == NULL)
517        {
518          qp = _Aldata._Head;
519        }
520
521        /* find the free cell location */
522        while ((nextCell = qp->_Next) != 0
523               && _PTR_NORM(nextCell) < _PTR_NORM(q))
524          qp = qp->_Next;
525      }
526
527#ifdef SPEEDUP_COMPARE
528      if (qp != savedQp)
529        printf("oops \n");
530#endif /* SPEEDUP_COMPARE */
531#endif /* SPEEDUP */
532
533#if !defined SPEEDUP || defined SPEEDUP_COMPARE
534      qp = savedQp;
535#endif /* SPEEDUP */
536
537      qpp = (char *)qp + qp->_Size;
538      if (_PTR_NORM((char *)q) < _PTR_NORM(qpp))
539        return; /* erroneous call, overlaps qp */
540      else if (_PTR_NORM(qpp) == _PTR_NORM((char *)q))
541      { /* merge qp and q (merge with prior cell) */
542        /* nothing to do to bin's free list here */
543        qp->_Size += q->_Size;
544        q = qp;
545      }
546      else if (qp->_Next != 0 && _PTR_NORM((char *)qp->_Next)
547               < _PTR_NORM((char *)q + q->_Size))
548        return; /* erroneous call, overlaps qp->_Next */
549      else
550      { /* splice q after qp */
551#ifdef SPEEDUP
552        /* add 1 entry here - this could change first entry in q's bin */
553        _Cell *firstFree = binsFirstFreeCell[binNum];
554        if ((firstFree == 0) || (q < firstFree))
555          binsFirstFreeCell[binNum] = q;
556#endif /* SPEEDUP */
557        q->_Next = qp->_Next;
558        qp->_Next = q;
559      }
560    }
561    if (q->_Next != 0 && _PTR_NORM((char *)q + q->_Size)
562        == _PTR_NORM((char *)q->_Next))
563    { /* merge q and q->_Next (merge with latter cell) */
564#ifdef SPEEDUP
565      /* lose 1 cell here - this could change first entry in bin.
566       * if q->_Next was first in bin, now it's its Next.
567       * careful : watch for next being in a different bin.
568       */
569      qNextBin = GET_MEM_BIN(q->_Next);
570
571      if (binsFirstFreeCell[qNextBin] == q->_Next)
572      {
573        /* The q->_Next cell is the first free cell in its bin; set the first free cell
574           pointer to NULL for now; if there is another free cell in the same bin then
575           the first free cell pointer will be updated in next 'if' code block */
576        binsFirstFreeCell[qNextBin] = 0;
577
578        /* If there is another free cell after q->_Next and it's in the same bin then
579           update the first free cell pointer if necessary */
580        if (0 != (q->_Next->_Next))
581        {
582          qNextNextBin = GET_MEM_BIN(q->_Next->_Next);
583
584          /* The first free cell pointer for q->_Next->_Next's bin can only be set to 0
585             by the code above; if it is 0 then q->_Next->_Next must be the first free
586             cell in the bin */
587          if (0 == binsFirstFreeCell[qNextNextBin])
588          {
589            binsFirstFreeCell[qNextNextBin] = q->_Next->_Next;
590          }
591        }
592      }
593#endif /* SPEEDUP */
594      _Aldata._Plast = 0; /* deoptimize for safety */
595      q->_Size += q->_Next->_Size;
596      q->_Next = q->_Next->_Next;
597    }
598    _UPD_Altab(0, -size, size); /* heap=alloc+free */
599    _OK_Altab(&_Aldata);
600    /* every successful free "falls off" here */
601  }
602  _STD_END
603
604#ifdef __cplusplus
605} /* end extern "C" */
606#endif
607
608#endif /* PORTABLE_DINKUM_MEM_MGR */
609
610
611