prmem.c (16043B)
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6 /* 7 ** Thread safe versions of malloc, free, realloc, calloc and cfree. 8 */ 9 10 #include "primpl.h" 11 12 #ifdef _PR_ZONE_ALLOCATOR 13 14 /* 15 ** The zone allocator code must use native mutexes and cannot 16 ** use PRLocks because PR_NewLock calls PR_Calloc, resulting 17 ** in cyclic dependency of initialization. 18 */ 19 20 # include <string.h> 21 22 union memBlkHdrUn; 23 24 typedef struct MemoryZoneStr { 25 union memBlkHdrUn* head; /* free list */ 26 pthread_mutex_t lock; 27 size_t blockSize; /* size of blocks on this free list */ 28 PRUint32 locked; /* current state of lock */ 29 PRUint32 contention; /* counter: had to wait for lock */ 30 PRUint32 hits; /* allocated from free list */ 31 PRUint32 misses; /* had to call malloc */ 32 PRUint32 elements; /* on free list */ 33 } MemoryZone; 34 35 typedef union memBlkHdrUn { 36 unsigned char filler[48]; /* fix the size of this beast */ 37 struct memBlkHdrStr { 38 union memBlkHdrUn* next; 39 MemoryZone* zone; 40 size_t blockSize; 41 size_t requestedSize; 42 PRUint32 magic; 43 } s; 44 } MemBlockHdr; 45 46 # define MEM_ZONES 7 47 # define THREAD_POOLS 11 /* prime number for modulus */ 48 # define ZONE_MAGIC 0x0BADC0DE 49 50 static MemoryZone zones[MEM_ZONES][THREAD_POOLS]; 51 52 static PRBool use_zone_allocator = PR_FALSE; 53 54 static void pr_ZoneFree(void* ptr); 55 56 void _PR_DestroyZones(void) { 57 int i, j; 58 59 if (!use_zone_allocator) { 60 return; 61 } 62 63 for (j = 0; j < THREAD_POOLS; j++) { 64 for (i = 0; i < MEM_ZONES; i++) { 65 MemoryZone* mz = &zones[i][j]; 66 pthread_mutex_destroy(&mz->lock); 67 while (mz->head) { 68 MemBlockHdr* hdr = mz->head; 69 mz->head = hdr->s.next; /* unlink it */ 70 free(hdr); 71 mz->elements--; 72 } 73 } 74 } 75 use_zone_allocator = PR_FALSE; 76 } 77 78 /* 79 ** pr_FindSymbolInProg 80 ** 81 ** Find the specified data symbol in the program and return 82 ** its address. 83 */ 84 85 # ifdef HAVE_DLL 86 87 # if defined(USE_DLFCN) && !defined(NO_DLOPEN_NULL) 88 89 # include <dlfcn.h> 90 91 static void* pr_FindSymbolInProg(const char* name) { 92 void* h; 93 void* sym; 94 95 h = dlopen(0, RTLD_LAZY); 96 if (h == NULL) { 97 return NULL; 98 } 99 sym = dlsym(h, name); 100 (void)dlclose(h); 101 return sym; 102 } 103 104 # elif defined(USE_HPSHL) 105 106 # include <dl.h> 107 108 static void* pr_FindSymbolInProg(const char* name) { 109 shl_t h = NULL; 110 void* sym; 111 112 if (shl_findsym(&h, name, TYPE_DATA, &sym) == -1) { 113 return NULL; 114 } 115 return sym; 116 } 117 118 # elif defined(USE_MACH_DYLD) || defined(NO_DLOPEN_NULL) 119 120 static void* pr_FindSymbolInProg(const char* name) { 121 /* FIXME: not implemented */ 122 return NULL; 123 } 124 125 # else 126 127 # error "The zone allocator is not supported on this platform" 128 129 # endif 130 131 # else /* !defined(HAVE_DLL) */ 132 133 static void* pr_FindSymbolInProg(const char* name) { 134 /* can't be implemented */ 135 return NULL; 136 } 137 138 # endif /* HAVE_DLL */ 139 140 void _PR_InitZones(void) { 141 int i, j; 142 char* envp; 143 PRBool* sym; 144 145 if ((sym = (PRBool*)pr_FindSymbolInProg("nspr_use_zone_allocator")) != NULL) { 146 use_zone_allocator = *sym; 147 } else if ((envp = getenv("NSPR_USE_ZONE_ALLOCATOR")) != NULL) { 148 use_zone_allocator = (atoi(envp) == 1); 149 } 150 151 if (!use_zone_allocator) { 152 return; 153 } 154 155 for (j = 0; j < THREAD_POOLS; j++) { 156 for (i = 0; i < MEM_ZONES; i++) { 157 MemoryZone* mz = &zones[i][j]; 158 int rv = pthread_mutex_init(&mz->lock, NULL); 159 PR_ASSERT(0 == rv); 160 if (rv != 0) { 161 goto loser; 162 } 163 mz->blockSize = 16 << (2 * i); 164 } 165 } 166 return; 167 168 loser: 169 _PR_DestroyZones(); 170 return; 171 } 172 173 PR_IMPLEMENT(void) 174 PR_FPrintZoneStats(PRFileDesc* debug_out) { 175 int i, j; 176 177 for (j = 0; j < THREAD_POOLS; j++) { 178 for (i = 0; i < MEM_ZONES; i++) { 179 MemoryZone* mz = &zones[i][j]; 180 MemoryZone zone = *mz; 181 if (zone.elements || zone.misses || zone.hits) { 182 PR_fprintf(debug_out, 183 "pool: %d, zone: %d, size: %d, free: %d, hit: %d, miss: %d, " 184 "contend: %d\n", 185 j, i, zone.blockSize, zone.elements, zone.hits, zone.misses, 186 zone.contention); 187 } 188 } 189 } 190 } 191 192 static void* pr_ZoneMalloc(PRUint32 size) { 193 void* rv; 194 unsigned int zone; 195 size_t blockSize; 196 MemBlockHdr *mb, *mt; 197 MemoryZone* mz; 198 199 /* Always allocate a non-zero amount of bytes */ 200 if (size < 1) { 201 size = 1; 202 } 203 for (zone = 0, blockSize = 16; zone < MEM_ZONES; ++zone, blockSize <<= 2) { 204 if (size <= blockSize) { 205 break; 206 } 207 } 208 if (zone < MEM_ZONES) { 209 pthread_t me = pthread_self(); 210 unsigned int pool = (PRUptrdiff)me % THREAD_POOLS; 211 PRUint32 wasLocked; 212 mz = &zones[zone][pool]; 213 wasLocked = mz->locked; 214 pthread_mutex_lock(&mz->lock); 215 mz->locked = 1; 216 if (wasLocked) { 217 mz->contention++; 218 } 219 if (mz->head) { 220 mb = mz->head; 221 PR_ASSERT(mb->s.magic == ZONE_MAGIC); 222 PR_ASSERT(mb->s.zone == mz); 223 PR_ASSERT(mb->s.blockSize == blockSize); 224 PR_ASSERT(mz->blockSize == blockSize); 225 226 mt = (MemBlockHdr*)(((char*)(mb + 1)) + blockSize); 227 PR_ASSERT(mt->s.magic == ZONE_MAGIC); 228 PR_ASSERT(mt->s.zone == mz); 229 PR_ASSERT(mt->s.blockSize == blockSize); 230 231 mz->hits++; 232 mz->elements--; 233 mz->head = mb->s.next; /* take off free list */ 234 mz->locked = 0; 235 pthread_mutex_unlock(&mz->lock); 236 237 mt->s.next = mb->s.next = NULL; 238 mt->s.requestedSize = mb->s.requestedSize = size; 239 240 rv = (void*)(mb + 1); 241 return rv; 242 } 243 244 mz->misses++; 245 mz->locked = 0; 246 pthread_mutex_unlock(&mz->lock); 247 248 mb = (MemBlockHdr*)malloc(blockSize + 2 * (sizeof *mb)); 249 if (!mb) { 250 PR_SetError(PR_OUT_OF_MEMORY_ERROR, 0); 251 return NULL; 252 } 253 mb->s.next = NULL; 254 mb->s.zone = mz; 255 mb->s.magic = ZONE_MAGIC; 256 mb->s.blockSize = blockSize; 257 mb->s.requestedSize = size; 258 259 mt = (MemBlockHdr*)(((char*)(mb + 1)) + blockSize); 260 memcpy(mt, mb, sizeof *mb); 261 262 rv = (void*)(mb + 1); 263 return rv; 264 } 265 266 /* size was too big. Create a block with no zone */ 267 blockSize = (size & 15) ? size + 16 - (size & 15) : size; 268 mb = (MemBlockHdr*)malloc(blockSize + 2 * (sizeof *mb)); 269 if (!mb) { 270 PR_SetError(PR_OUT_OF_MEMORY_ERROR, 0); 271 return NULL; 272 } 273 mb->s.next = NULL; 274 mb->s.zone = NULL; 275 mb->s.magic = ZONE_MAGIC; 276 mb->s.blockSize = blockSize; 277 mb->s.requestedSize = size; 278 279 mt = (MemBlockHdr*)(((char*)(mb + 1)) + blockSize); 280 memcpy(mt, mb, sizeof *mb); 281 282 rv = (void*)(mb + 1); 283 return rv; 284 } 285 286 static void* pr_ZoneCalloc(PRUint32 nelem, PRUint32 elsize) { 287 PRUint32 size = nelem * elsize; 288 void* p = pr_ZoneMalloc(size); 289 if (p) { 290 memset(p, 0, size); 291 } 292 return p; 293 } 294 295 static void* pr_ZoneRealloc(void* oldptr, PRUint32 bytes) { 296 void* rv; 297 MemBlockHdr* mb; 298 int ours; 299 MemBlockHdr phony; 300 301 if (!oldptr) { 302 return pr_ZoneMalloc(bytes); 303 } 304 mb = (MemBlockHdr*)((char*)oldptr - (sizeof *mb)); 305 if (mb->s.magic != ZONE_MAGIC) { 306 /* Maybe this just came from ordinary malloc */ 307 # ifdef DEBUG 308 fprintf(stderr, 309 "Warning: reallocing memory block %p from ordinary malloc\n", 310 oldptr); 311 # endif 312 /* 313 * We are going to realloc oldptr. If realloc succeeds, the 314 * original value of oldptr will point to freed memory. So this 315 * function must not fail after a successfull realloc call. We 316 * must perform any operation that may fail before the realloc 317 * call. 318 */ 319 rv = pr_ZoneMalloc(bytes); /* this may fail */ 320 if (!rv) { 321 return rv; 322 } 323 324 /* We don't know how big it is. But we can fix that. */ 325 oldptr = realloc(oldptr, bytes); 326 /* 327 * If realloc returns NULL, this function loses the original 328 * value of oldptr. This isn't a leak because the caller of 329 * this function still has the original value of oldptr. 330 */ 331 if (!oldptr) { 332 if (bytes) { 333 PR_SetError(PR_OUT_OF_MEMORY_ERROR, 0); 334 pr_ZoneFree(rv); 335 return oldptr; 336 } 337 } 338 phony.s.requestedSize = bytes; 339 mb = &phony; 340 ours = 0; 341 } else { 342 size_t blockSize = mb->s.blockSize; 343 MemBlockHdr* mt = (MemBlockHdr*)(((char*)(mb + 1)) + blockSize); 344 345 PR_ASSERT(mt->s.magic == ZONE_MAGIC); 346 PR_ASSERT(mt->s.zone == mb->s.zone); 347 PR_ASSERT(mt->s.blockSize == blockSize); 348 349 if (bytes <= blockSize) { 350 /* The block is already big enough. */ 351 mt->s.requestedSize = mb->s.requestedSize = bytes; 352 return oldptr; 353 } 354 ours = 1; 355 rv = pr_ZoneMalloc(bytes); 356 if (!rv) { 357 return rv; 358 } 359 } 360 361 if (oldptr && mb->s.requestedSize) { 362 memcpy(rv, oldptr, mb->s.requestedSize); 363 } 364 if (ours) { 365 pr_ZoneFree(oldptr); 366 } else if (oldptr) { 367 free(oldptr); 368 } 369 return rv; 370 } 371 372 static void pr_ZoneFree(void* ptr) { 373 MemBlockHdr *mb, *mt; 374 MemoryZone* mz; 375 size_t blockSize; 376 PRUint32 wasLocked; 377 378 if (!ptr) { 379 return; 380 } 381 382 mb = (MemBlockHdr*)((char*)ptr - (sizeof *mb)); 383 384 if (mb->s.magic != ZONE_MAGIC) { 385 /* maybe this came from ordinary malloc */ 386 # ifdef DEBUG 387 fprintf(stderr, "Warning: freeing memory block %p from ordinary malloc\n", 388 ptr); 389 # endif 390 free(ptr); 391 return; 392 } 393 394 blockSize = mb->s.blockSize; 395 mz = mb->s.zone; 396 mt = (MemBlockHdr*)(((char*)(mb + 1)) + blockSize); 397 PR_ASSERT(mt->s.magic == ZONE_MAGIC); 398 PR_ASSERT(mt->s.zone == mz); 399 PR_ASSERT(mt->s.blockSize == blockSize); 400 if (!mz) { 401 PR_ASSERT(blockSize > 65536); 402 /* This block was not in any zone. Just free it. */ 403 free(mb); 404 return; 405 } 406 PR_ASSERT(mz->blockSize == blockSize); 407 wasLocked = mz->locked; 408 pthread_mutex_lock(&mz->lock); 409 mz->locked = 1; 410 if (wasLocked) { 411 mz->contention++; 412 } 413 mt->s.next = mb->s.next = mz->head; /* put on head of list */ 414 mz->head = mb; 415 mz->elements++; 416 mz->locked = 0; 417 pthread_mutex_unlock(&mz->lock); 418 } 419 420 PR_IMPLEMENT(void*) PR_Malloc(PRUint32 size) { 421 if (!_pr_initialized) { 422 _PR_ImplicitInitialization(); 423 } 424 425 return use_zone_allocator ? pr_ZoneMalloc(size) : malloc(size); 426 } 427 428 PR_IMPLEMENT(void*) PR_Calloc(PRUint32 nelem, PRUint32 elsize) { 429 if (!_pr_initialized) { 430 _PR_ImplicitInitialization(); 431 } 432 433 return use_zone_allocator ? pr_ZoneCalloc(nelem, elsize) 434 : calloc(nelem, elsize); 435 } 436 437 PR_IMPLEMENT(void*) PR_Realloc(void* ptr, PRUint32 size) { 438 if (!_pr_initialized) { 439 _PR_ImplicitInitialization(); 440 } 441 442 return use_zone_allocator ? pr_ZoneRealloc(ptr, size) : realloc(ptr, size); 443 } 444 445 PR_IMPLEMENT(void) PR_Free(void* ptr) { 446 if (use_zone_allocator) { 447 pr_ZoneFree(ptr); 448 } else { 449 free(ptr); 450 } 451 } 452 453 #else /* !defined(_PR_ZONE_ALLOCATOR) */ 454 455 /* 456 ** The PR_Malloc, PR_Calloc, PR_Realloc, and PR_Free functions simply 457 ** call their libc equivalents now. This may seem redundant, but it 458 ** ensures that we are calling into the same runtime library. On 459 ** Win32, it is possible to have multiple runtime libraries (e.g., 460 ** objects compiled with /MD and /MDd) in the same process, and 461 ** they maintain separate heaps, which cannot be mixed. 462 */ 463 PR_IMPLEMENT(void*) PR_Malloc(PRUint32 size) { return malloc(size); } 464 465 PR_IMPLEMENT(void*) PR_Calloc(PRUint32 nelem, PRUint32 elsize) { 466 return calloc(nelem, elsize); 467 } 468 469 PR_IMPLEMENT(void*) PR_Realloc(void* ptr, PRUint32 size) { 470 return realloc(ptr, size); 471 } 472 473 PR_IMPLEMENT(void) PR_Free(void* ptr) { free(ptr); } 474 475 #endif /* _PR_ZONE_ALLOCATOR */ 476 477 /* 478 ** Complexity alert! 479 ** 480 ** If malloc/calloc/free (etc.) were implemented to use pr lock's then 481 ** the entry points could block when called if some other thread had the 482 ** lock. 483 ** 484 ** Most of the time this isn't a problem. However, in the case that we 485 ** are using the thread safe malloc code after PR_Init but before 486 ** PR_AttachThread has been called (on a native thread that nspr has yet 487 ** to be told about) we could get royally screwed if the lock was busy 488 ** and we tried to context switch the thread away. In this scenario 489 ** PR_CURRENT_THREAD() == NULL 490 ** 491 ** To avoid this unfortunate case, we use the low level locking 492 ** facilities for malloc protection instead of the slightly higher level 493 ** locking. This makes malloc somewhat faster so maybe it's a good thing 494 ** anyway. 495 */ 496 #ifdef _PR_OVERRIDE_MALLOC 497 498 /* Imports */ 499 extern void* _PR_UnlockedMalloc(size_t size); 500 extern void* _PR_UnlockedMemalign(size_t alignment, size_t size); 501 extern void _PR_UnlockedFree(void* ptr); 502 extern void* _PR_UnlockedRealloc(void* ptr, size_t size); 503 extern void* _PR_UnlockedCalloc(size_t n, size_t elsize); 504 505 static PRBool _PR_malloc_initialised = PR_FALSE; 506 507 # ifdef _PR_PTHREADS 508 static pthread_mutex_t _PR_MD_malloc_crustylock; 509 510 # define _PR_Lock_Malloc() \ 511 { \ 512 if (PR_TRUE == _PR_malloc_initialised) { \ 513 PRStatus rv; \ 514 rv = pthread_mutex_lock(&_PR_MD_malloc_crustylock); \ 515 PR_ASSERT(0 == rv); \ 516 } 517 518 # define _PR_Unlock_Malloc() \ 519 if (PR_TRUE == _PR_malloc_initialised) { \ 520 PRStatus rv; \ 521 rv = pthread_mutex_unlock(&_PR_MD_malloc_crustylock); \ 522 PR_ASSERT(0 == rv); \ 523 } \ 524 } 525 # else /* _PR_PTHREADS */ 526 static _MDLock _PR_MD_malloc_crustylock; 527 528 # define _PR_Lock_Malloc() \ 529 { \ 530 PRIntn _is; \ 531 if (PR_TRUE == _PR_malloc_initialised) { \ 532 if (_PR_MD_CURRENT_THREAD() && \ 533 !_PR_IS_NATIVE_THREAD(_PR_MD_CURRENT_THREAD())) \ 534 _PR_INTSOFF(_is); \ 535 _PR_MD_LOCK(&_PR_MD_malloc_crustylock); \ 536 } 537 538 # define _PR_Unlock_Malloc() \ 539 if (PR_TRUE == _PR_malloc_initialised) { \ 540 _PR_MD_UNLOCK(&_PR_MD_malloc_crustylock); \ 541 if (_PR_MD_CURRENT_THREAD() && \ 542 !_PR_IS_NATIVE_THREAD(_PR_MD_CURRENT_THREAD())) \ 543 _PR_INTSON(_is); \ 544 } \ 545 } 546 # endif /* _PR_PTHREADS */ 547 548 PR_IMPLEMENT(PRStatus) _PR_MallocInit(void) { 549 PRStatus rv = PR_SUCCESS; 550 551 if (PR_TRUE == _PR_malloc_initialised) { 552 return PR_SUCCESS; 553 } 554 555 # ifdef _PR_PTHREADS 556 { 557 int status; 558 pthread_mutexattr_t mattr; 559 560 status = _PT_PTHREAD_MUTEXATTR_INIT(&mattr); 561 PR_ASSERT(0 == status); 562 status = _PT_PTHREAD_MUTEX_INIT(_PR_MD_malloc_crustylock, mattr); 563 PR_ASSERT(0 == status); 564 status = _PT_PTHREAD_MUTEXATTR_DESTROY(&mattr); 565 PR_ASSERT(0 == status); 566 } 567 # else /* _PR_PTHREADS */ 568 _MD_NEW_LOCK(&_PR_MD_malloc_crustylock); 569 # endif /* _PR_PTHREADS */ 570 571 if (PR_SUCCESS == rv) { 572 _PR_malloc_initialised = PR_TRUE; 573 } 574 575 return rv; 576 } 577 578 void* malloc(size_t size) { 579 void* p; 580 _PR_Lock_Malloc(); 581 p = _PR_UnlockedMalloc(size); 582 _PR_Unlock_Malloc(); 583 return p; 584 } 585 586 void free(void* ptr) { 587 _PR_Lock_Malloc(); 588 _PR_UnlockedFree(ptr); 589 _PR_Unlock_Malloc(); 590 } 591 592 void* realloc(void* ptr, size_t size) { 593 void* p; 594 _PR_Lock_Malloc(); 595 p = _PR_UnlockedRealloc(ptr, size); 596 _PR_Unlock_Malloc(); 597 return p; 598 } 599 600 void* calloc(size_t n, size_t elsize) { 601 void* p; 602 _PR_Lock_Malloc(); 603 p = _PR_UnlockedCalloc(n, elsize); 604 _PR_Unlock_Malloc(); 605 return p; 606 } 607 608 void cfree(void* p) { 609 _PR_Lock_Malloc(); 610 _PR_UnlockedFree(p); 611 _PR_Unlock_Malloc(); 612 } 613 614 void _PR_InitMem(void) { 615 PRStatus rv; 616 rv = _PR_MallocInit(); 617 PR_ASSERT(PR_SUCCESS == rv); 618 } 619 620 #endif /* _PR_OVERRIDE_MALLOC */