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 40 extern "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) 120 static _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