Panda3D
memoryHook.cxx
1 // Filename: memoryHook.cxx
2 // Created by: drose (28Jun07)
3 //
4 ////////////////////////////////////////////////////////////////////
5 //
6 // PANDA 3D SOFTWARE
7 // Copyright (c) Carnegie Mellon University. All rights reserved.
8 //
9 // All use of this software is subject to the terms of the revised BSD
10 // license. You should have received a copy of this license along
11 // with this source code in a file named "LICENSE."
12 //
13 ////////////////////////////////////////////////////////////////////
14 
15 #include "memoryHook.h"
16 #include "deletedBufferChain.h"
17 #include <stdlib.h>
18 
19 #ifdef WIN32
20 
21 // Windows case.
22 #ifndef WIN32_LEAN_AND_MEAN
23 #define WIN32_LEAN_AND_MEAN 1
24 #endif
25 #include <windows.h>
26 
27 #else
28 
29 // Posix case.
30 #include <unistd.h>
31 #include <sys/types.h>
32 #include <sys/mman.h>
33 
34 #ifndef MAP_ANON
35 #define MAP_ANON 0x1000
36 #endif
37 
38 #endif // WIN32
39 
40 
41 #if defined(USE_MEMORY_DLMALLOC)
42 
43 /////////////////////////////////////////////////////////////////////
44 //
45 // Memory manager: DLMALLOC
46 //
47 // This is Doug Lea's memory manager. It is very fast, but it is not
48 // thread-safe. However, we provide thread locking within MemoryHook.
49 //
50 /////////////////////////////////////////////////////////////////////
51 
52 #define USE_DL_PREFIX 1
53 #define NO_MALLINFO 1
54 #ifdef _DEBUG
55  #define DEBUG 1
56 #endif
57 #ifdef assert
58  // dlmalloc defines its own assert, which clashes.
59  #undef assert
60 #endif
61 #include "dlmalloc.h"
62 #include "dlmalloc_src.cxx"
63 
64 #define call_malloc dlmalloc
65 #define call_realloc dlrealloc
66 #define call_free dlfree
67 #define MEMORY_HOOK_MALLOC_LOCK 1
68 
69 #elif defined(USE_MEMORY_PTMALLOC2)
70 // This doesn't appear to work in Linux; perhaps it is clashing with
71 // the system library. It also doesn't appear to be thread-safe on
72 // OSX.
73 
74 /////////////////////////////////////////////////////////////////////
75 //
76 // Memory manager: PTMALLOC2
77 //
78 // Ptmalloc2 is a derivative of Doug Lea's memory manager that was
79 // made thread-safe by Wolfram Gloger, then was ported to windows by
80 // Niall Douglas. It is not quite as fast as dlmalloc (because the
81 // thread-safety constructs take a certain amount of CPU time), but
82 // it's still much faster than the windows allocator.
83 //
84 /////////////////////////////////////////////////////////////////////
85 
86 #define USE_DL_PREFIX 1
87 #define NO_MALLINFO 1
88 #ifdef _DEBUG
89  #define MALLOC_DEBUG 2
90 #endif
91 #include "ptmalloc2_smp_src.cxx"
92 
93 #define call_malloc dlmalloc
94 #define call_realloc dlrealloc
95 #define call_free dlfree
96 #undef MEMORY_HOOK_MALLOC_LOCK
97 
98 #else
99 
100 /////////////////////////////////////////////////////////////////////
101 //
102 // Memory manager: MALLOC
103 //
104 // This option uses the built-in system allocator. This is a good
105 // choice on linux, but it's a terrible choice on windows.
106 //
107 /////////////////////////////////////////////////////////////////////
108 
109 #define call_malloc malloc
110 #define call_realloc realloc
111 #define call_free free
112 #undef MEMORY_HOOK_MALLOC_LOCK
113 
114 #endif // USE_MEMORY_*
115 
116 ////////////////////////////////////////////////////////////////////
117 // Function: MemoryHook::Constructor
118 // Access: Public
119 // Description:
120 ////////////////////////////////////////////////////////////////////
121 MemoryHook::
122 MemoryHook() {
123 #ifdef WIN32
124 
125  // Windows case.
126  SYSTEM_INFO sysinfo;
127  GetSystemInfo(&sysinfo);
128 
129  _page_size = (size_t)sysinfo.dwPageSize;
130 
131 #else
132 
133  // Posix case.
134  _page_size = sysconf(_SC_PAGESIZE);
135 
136 #endif // WIN32
137 
138 #ifdef DO_MEMORY_USAGE
139  _total_heap_single_size = 0;
140  _total_heap_array_size = 0;
141  _requested_heap_size = 0;
142  _total_mmap_size = 0;
143  _max_heap_size = ~(size_t)0;
144 #endif
145 }
146 
147 ////////////////////////////////////////////////////////////////////
148 // Function: MemoryHook::Copy Constructor
149 // Access: Public
150 // Description:
151 ////////////////////////////////////////////////////////////////////
152 MemoryHook::
153 MemoryHook(const MemoryHook &copy) :
154  _page_size(copy._page_size)
155 {
156 #ifdef DO_MEMORY_USAGE
157  _total_heap_single_size = copy._total_heap_single_size;
158  _total_heap_array_size = copy._total_heap_array_size;
159  _requested_heap_size = copy._requested_heap_size;
160  _total_mmap_size = copy._total_mmap_size;
161  _max_heap_size = copy._max_heap_size;
162 #endif
163 
164  ((MutexImpl &)copy._lock).acquire();
165  _deleted_chains = copy._deleted_chains;
166  ((MutexImpl &)copy._lock).release();
167 }
168 
169 ////////////////////////////////////////////////////////////////////
170 // Function: MemoryHook::Destructor
171 // Access: Public, Virtual
172 // Description:
173 ////////////////////////////////////////////////////////////////////
174 MemoryHook::
175 ~MemoryHook() {
176  // Really, we only have this destructor to shut up gcc about the
177  // virtual functions warning.
178 }
179 
180 ////////////////////////////////////////////////////////////////////
181 // Function: MemoryHook::heap_alloc_single
182 // Access: Public, Virtual
183 // Description: Allocates a block of memory from the heap, similar to
184 // malloc(). This will never return NULL; it will abort
185 // instead if memory is not available.
186 //
187 // This particular function should be used to allocate
188 // memory for a single object, as opposed to an array.
189 // The only difference is in the bookkeeping.
190 ////////////////////////////////////////////////////////////////////
191 void *MemoryHook::
192 heap_alloc_single(size_t size) {
193  size_t inflated_size = inflate_size(size);
194 
195 #ifdef MEMORY_HOOK_MALLOC_LOCK
196  _lock.acquire();
197  void *alloc = call_malloc(inflated_size);
198  _lock.release();
199 #else
200  void *alloc = call_malloc(inflated_size);
201 #endif
202 
203  while (alloc == (void *)NULL) {
204  alloc_fail(inflated_size);
205 #ifdef MEMORY_HOOK_MALLOC_LOCK
206  _lock.acquire();
207  alloc = call_malloc(inflated_size);
208  _lock.release();
209 #else
210  alloc = call_malloc(inflated_size);
211 #endif
212  }
213 
214 #ifdef DO_MEMORY_USAGE
215  // In the DO_MEMORY_USAGE case, we want to track the total size of
216  // allocated bytes on the heap.
217  AtomicAdjust::add(_total_heap_single_size, (AtomicAdjust::Integer)size);
218  if ((size_t)AtomicAdjust::get(_total_heap_single_size) +
219  (size_t)AtomicAdjust::get(_total_heap_array_size) >
220  _max_heap_size) {
221  overflow_heap_size();
222  }
223 #endif // DO_MEMORY_USAGE
224 
225  void *ptr = alloc_to_ptr(alloc, size);
226  assert(ptr >= alloc && (char *)ptr + size <= (char *)alloc + inflated_size);
227  return ptr;
228 }
229 
230 ////////////////////////////////////////////////////////////////////
231 // Function: MemoryHook::heap_free_single
232 // Access: Public, Virtual
233 // Description: Releases a block of memory previously allocated via
234 // heap_alloc_single.
235 ////////////////////////////////////////////////////////////////////
236 void MemoryHook::
237 heap_free_single(void *ptr) {
238  size_t size;
239  void *alloc = ptr_to_alloc(ptr, size);
240 
241 #ifdef DO_MEMORY_USAGE
242  assert((int)size <= _total_heap_single_size);
243  AtomicAdjust::add(_total_heap_single_size, -(AtomicAdjust::Integer)size);
244 #endif // DO_MEMORY_USAGE
245 
246 #ifdef MEMORY_HOOK_MALLOC_LOCK
247  _lock.acquire();
248  call_free(alloc);
249  _lock.release();
250 #else
251  call_free(alloc);
252 #endif
253 }
254 
255 ////////////////////////////////////////////////////////////////////
256 // Function: MemoryHook::heap_alloc_array
257 // Access: Public, Virtual
258 // Description: Allocates a block of memory from the heap, similar to
259 // malloc(). This will never return NULL; it will abort
260 // instead if memory is not available.
261 //
262 // This particular function should be used to allocate
263 // memory for an array of objects, as opposed to a
264 // single object. The only difference is in the
265 // bookkeeping.
266 ////////////////////////////////////////////////////////////////////
267 void *MemoryHook::
268 heap_alloc_array(size_t size) {
269  size_t inflated_size = inflate_size(size);
270 
271 #ifdef MEMORY_HOOK_MALLOC_LOCK
272  _lock.acquire();
273  void *alloc = call_malloc(inflated_size);
274  _lock.release();
275 #else
276  void *alloc = call_malloc(inflated_size);
277 #endif
278 
279  while (alloc == (void *)NULL) {
280  alloc_fail(inflated_size);
281 #ifdef MEMORY_HOOK_MALLOC_LOCK
282  _lock.acquire();
283  alloc = call_malloc(inflated_size);
284  _lock.release();
285 #else
286  alloc = call_malloc(inflated_size);
287 #endif
288  }
289 
290 #ifdef DO_MEMORY_USAGE
291  // In the DO_MEMORY_USAGE case, we want to track the total size of
292  // allocated bytes on the heap.
293  AtomicAdjust::add(_total_heap_array_size, (AtomicAdjust::Integer)size);
294  if ((size_t)AtomicAdjust::get(_total_heap_single_size) +
295  (size_t)AtomicAdjust::get(_total_heap_array_size) >
296  _max_heap_size) {
297  overflow_heap_size();
298  }
299 #endif // DO_MEMORY_USAGE
300 
301  void *ptr = alloc_to_ptr(alloc, size);
302  assert(ptr >= alloc && (char *)ptr + size <= (char *)alloc + inflated_size);
303  return ptr;
304 }
305 
306 ////////////////////////////////////////////////////////////////////
307 // Function: MemoryHook::heap_realloc_array
308 // Access: Public, Virtual
309 // Description: Resizes a block of memory previously returned from
310 // heap_alloc_array.
311 ////////////////////////////////////////////////////////////////////
312 void *MemoryHook::
313 heap_realloc_array(void *ptr, size_t size) {
314  size_t orig_size;
315  void *alloc = ptr_to_alloc(ptr, orig_size);
316 
317 #ifdef DO_MEMORY_USAGE
318  assert((AtomicAdjust::Integer)orig_size <= _total_heap_array_size);
319  AtomicAdjust::add(_total_heap_array_size, (AtomicAdjust::Integer)size-(AtomicAdjust::Integer)orig_size);
320 #endif // DO_MEMORY_USAGE
321 
322  size_t inflated_size = inflate_size(size);
323 
324  void *alloc1 = alloc;
325 #ifdef MEMORY_HOOK_MALLOC_LOCK
326  _lock.acquire();
327  alloc1 = call_realloc(alloc1, inflated_size);
328  _lock.release();
329 #else
330  alloc1 = call_realloc(alloc1, inflated_size);
331 #endif
332 
333  while (alloc1 == (void *)NULL) {
334  alloc_fail(inflated_size);
335 
336  // Recover the original pointer.
337  alloc1 = alloc;
338 
339 #ifdef MEMORY_HOOK_MALLOC_LOCK
340  _lock.acquire();
341  alloc1 = call_realloc(alloc1, inflated_size);
342  _lock.release();
343 #else
344  alloc1 = call_realloc(alloc1, inflated_size);
345 #endif
346  }
347 
348  void *ptr1 = alloc_to_ptr(alloc1, size);
349  assert(ptr1 >= alloc1 && (char *)ptr1 + size <= (char *)alloc1 + inflated_size);
350 #if defined(MEMORY_HOOK_DO_ALIGN)
351  // We might have to shift the memory to account for the new offset
352  // due to the alignment.
353  size_t orig_delta = (char *)ptr - (char *)alloc;
354  size_t new_delta = (char *)ptr1 - (char *)alloc1;
355  if (orig_delta != new_delta) {
356  memmove((char *)alloc1 + new_delta, (char *)alloc1 + orig_delta, min(size, orig_size));
357  }
358 #endif // MEMORY_HOOK_DO_ALIGN
359  return ptr1;
360 }
361 
362 ////////////////////////////////////////////////////////////////////
363 // Function: MemoryHook::heap_free_array
364 // Access: Public, Virtual
365 // Description: Releases a block of memory previously allocated via
366 // heap_alloc_array.
367 ////////////////////////////////////////////////////////////////////
368 void MemoryHook::
369 heap_free_array(void *ptr) {
370  size_t size;
371  void *alloc = ptr_to_alloc(ptr, size);
372 
373 #ifdef DO_MEMORY_USAGE
374  assert((int)size <= _total_heap_array_size);
375  AtomicAdjust::add(_total_heap_array_size, -(AtomicAdjust::Integer)size);
376 #endif // DO_MEMORY_USAGE
377 
378 #ifdef MEMORY_HOOK_MALLOC_LOCK
379  _lock.acquire();
380  call_free(alloc);
381  _lock.release();
382 #else
383  call_free(alloc);
384 #endif
385 }
386 
387 ////////////////////////////////////////////////////////////////////
388 // Function: MemoryHook::heap_trim
389 // Access: Public
390 // Description: Attempts to release memory back to the system, if
391 // possible. The pad argument is the minimum amount of
392 // unused memory to keep in the heap (against future
393 // allocations). Any memory above that may be released
394 // to the system, reducing the memory size of this
395 // process. There is no guarantee that any memory may
396 // be released.
397 //
398 // Returns true if any memory was actually released,
399 // false otherwise.
400 ////////////////////////////////////////////////////////////////////
401 bool MemoryHook::
402 heap_trim(size_t pad) {
403  bool trimmed = false;
404 
405 #if defined(USE_MEMORY_DLMALLOC) || defined(USE_MEMORY_PTMALLOC2)
406  // Since malloc_trim() isn't standard C, we can't be sure it exists
407  // on a given platform. But if we're using dlmalloc, we know we
408  // have dlmalloc_trim.
409  _lock.acquire();
410  if (dlmalloc_trim(pad)) {
411  trimmed = true;
412  }
413  _lock.release();
414 #endif
415 
416 #ifdef WIN32
417  // Also, on Windows we have _heapmin().
418  if (_heapmin() == 0) {
419  trimmed = true;
420  }
421 #endif
422 
423  return trimmed;
424 }
425 
426 ////////////////////////////////////////////////////////////////////
427 // Function: MemoryHook::mmap_alloc
428 // Access: Public, Virtual
429 // Description: Allocates a raw page or pages of memory directly from
430 // the OS. This will be in a different address space
431 // from the memory allocated by heap_alloc(), and so it
432 // won't contribute to fragmentation of that memory.
433 //
434 // The allocation size must be an integer multiple of
435 // the page size. Use round_to_page_size() if there is
436 // any doubt.
437 //
438 // If allow_exec is true, the memory will be flagged so
439 // that it is legal to execute code that has been
440 // written to this memory.
441 ////////////////////////////////////////////////////////////////////
442 void *MemoryHook::
443 mmap_alloc(size_t size, bool allow_exec) {
444  assert((size % _page_size) == 0);
445 
446 #ifdef DO_MEMORY_USAGE
447  _total_mmap_size += size;
448 #endif
449 
450 #ifdef WIN32
451 
452  // Windows case.
453  void *ptr = VirtualAlloc(NULL, size, MEM_COMMIT | MEM_RESERVE,
454  allow_exec ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE);
455  if (ptr == (void *)NULL) {
456  DWORD err = GetLastError();
457  cerr << "Couldn't allocate memory page of size " << size << ": ";
458 
459  PVOID buffer;
460  DWORD length =
461  FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
462  NULL, err, 0, (LPTSTR)&buffer, 0, NULL);
463  if (length != 0) {
464  cerr << (char *)buffer << "\n";
465  } else {
466  cerr << "Error code " << err << "\n";
467  }
468  LocalFree(buffer);
469  abort();
470  }
471 
472  return ptr;
473 
474 #else
475 
476  // Posix case.
477  int prot = PROT_READ | PROT_WRITE;
478  if (allow_exec) {
479  prot |= PROT_EXEC;
480  }
481  void *ptr = mmap(NULL, size, prot, MAP_PRIVATE | MAP_ANON, -1, 0);
482  if (ptr == (void *)-1) {
483  perror("mmap");
484  abort();
485  }
486 
487  return ptr;
488 
489 #endif // WIN32
490 }
491 
492 ////////////////////////////////////////////////////////////////////
493 // Function: MemoryHook::mmap_free
494 // Access: Public, Virtual
495 // Description: Frees a block of memory previously allocated via
496 // mmap_alloc(). You must know how large the block was.
497 ////////////////////////////////////////////////////////////////////
498 void MemoryHook::
499 mmap_free(void *ptr, size_t size) {
500  assert((size % _page_size) == 0);
501 
502 #ifdef DO_MEMORY_USAGE
503  assert((int)size <= _total_mmap_size);
504  _total_mmap_size -= size;
505 #endif
506 
507 #ifdef WIN32
508  VirtualFree(ptr, 0, MEM_RELEASE);
509 #else
510  munmap(ptr, size);
511 #endif
512 }
513 
514 ////////////////////////////////////////////////////////////////////
515 // Function: MemoryHook::mark_pointer
516 // Access: Public, Virtual
517 // Description: This special method exists only to provide a callback
518 // hook into MemoryUsage. It indicates that the
519 // indicated pointer, allocated from somewhere other
520 // than a call to heap_alloc(), now contains a pointer
521 // to the indicated ReferenceCount object. If orig_size
522 // is 0, it indicates that the ReferenceCount object has
523 // been destroyed.
524 ////////////////////////////////////////////////////////////////////
525 void MemoryHook::
526 mark_pointer(void *, size_t, ReferenceCount *) {
527 }
528 
529 ////////////////////////////////////////////////////////////////////
530 // Function: MemoryHook::get_deleted_chain
531 // Access: Public
532 // Description: Returns a pointer to a global DeletedBufferChain
533 // object suitable for allocating arrays of the
534 // indicated size. There is one unique
535 // DeletedBufferChain object for every different size.
536 ////////////////////////////////////////////////////////////////////
538 get_deleted_chain(size_t buffer_size) {
539  DeletedBufferChain *chain;
540 
541  _lock.acquire();
542  DeletedChains::iterator dci = _deleted_chains.find(buffer_size);
543  if (dci != _deleted_chains.end()) {
544  chain = (*dci).second;
545  } else {
546  // Once allocated, this DeletedBufferChain object is never deleted.
547  chain = new DeletedBufferChain(buffer_size);
548  _deleted_chains.insert(DeletedChains::value_type(buffer_size, chain));
549  }
550 
551  _lock.release();
552  return chain;
553 }
554 
555 ////////////////////////////////////////////////////////////////////
556 // Function: MemoryHook::alloc_fail
557 // Access: Protected, Virtual
558 // Description: This callback method is called whenever a low-level
559 // call to call_malloc() has returned NULL, indicating
560 // failure.
561 //
562 // Since this method is called very low-level, and may
563 // be in the middle of any number of critical sections,
564 // it will be difficult for this callback initiate any
565 // emergency high-level operation to make more memory
566 // available. However, this module is set up to assume
567 // that that's what this method does, and will make
568 // another alloc attempt after it returns. Probably the
569 // only sensible thing this method can do, however, is
570 // just to display a message and abort.
571 ////////////////////////////////////////////////////////////////////
572 void MemoryHook::
573 alloc_fail(size_t attempted_size) {
574  cerr << "Out of memory allocating " << attempted_size << " bytes\n";
575  abort();
576 }
577 
578 #ifdef DO_MEMORY_USAGE
579 ////////////////////////////////////////////////////////////////////
580 // Function: MemoryHook::overflow_heap_size
581 // Access: Protected, Virtual
582 // Description: This callback method is called whenever the total
583 // allocated heap size exceeds _max_heap_size. It's
584 // mainly intended for reporting memory leaks, on the
585 // assumption that once we cross some specified
586 // threshold, we're just leaking memory.
587 //
588 // The implementation for this method is in MemoryUsage.
589 ////////////////////////////////////////////////////////////////////
590 void MemoryHook::
591 overflow_heap_size() {
592  _max_heap_size = ~(size_t)0;
593 }
594 #endif // DO_MEMORY_USAGE
DeletedBufferChain * get_deleted_chain(size_t buffer_size)
Returns a pointer to a global DeletedBufferChain object suitable for allocating arrays of the indicat...
virtual void heap_free_array(void *ptr)
Releases a block of memory previously allocated via heap_alloc_array.
virtual void * heap_alloc_single(size_t size)
Allocates a block of memory from the heap, similar to malloc().
virtual void mark_pointer(void *ptr, size_t orig_size, ReferenceCount *ref_ptr)
This special method exists only to provide a callback hook into MemoryUsage.
static void add(Integer &var, Integer delta)
Atomically computes var += delta.
virtual void mmap_free(void *ptr, size_t size)
Frees a block of memory previously allocated via mmap_alloc().
virtual void heap_free_single(void *ptr)
Releases a block of memory previously allocated via heap_alloc_single.
static Integer get(const Integer &var)
Atomically retrieves the snapshot value of the indicated variable.
A base class for all things that want to be reference-counted.
virtual void * heap_alloc_array(size_t size)
Allocates a block of memory from the heap, similar to malloc().
This class provides a wrapper around the various possible malloc schemes Panda might employ...
Definition: memoryHook.h:43
virtual void * heap_realloc_array(void *ptr, size_t size)
Resizes a block of memory previously returned from heap_alloc_array.
A fake mutex implementation for single-threaded applications that don&#39;t need any synchronization cont...
virtual void alloc_fail(size_t attempted_size)
This callback method is called whenever a low-level call to call_malloc() has returned NULL...
This template class can be used to provide faster allocation/deallocation for many Panda objects...
virtual void * mmap_alloc(size_t size, bool allow_exec)
Allocates a raw page or pages of memory directly from the OS.
bool heap_trim(size_t pad)
Attempts to release memory back to the system, if possible.