allocator_shim.cc revision ddb351dbec246cf1fab5ec20d2d5520909041de1
1// Copyright (c) 2011 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "base/allocator/allocator_shim.h"
6
7#include <config.h>
8
9// When defined, different heap allocators can be used via an environment
10// variable set before running the program.  This may reduce the amount
11// of inlining that we get with malloc/free/etc.  Disabling makes it
12// so that only tcmalloc can be used.
13#define ENABLE_DYNAMIC_ALLOCATOR_SWITCHING
14
15// TODO(mbelshe): Ensure that all calls to tcmalloc have the proper call depth
16// from the "user code" so that debugging tools (HeapChecker) can work.
17
18// __THROW is defined in glibc systems.  It means, counter-intuitively,
19// "This function will never throw an exception."  It's an optional
20// optimization tool, but we may need to use it to match glibc prototypes.
21#ifndef __THROW    // I guess we're not on a glibc system
22# define __THROW   // __THROW is just an optimization, so ok to make it ""
23#endif
24
25// new_mode behaves similarly to MSVC's _set_new_mode.
26// If flag is 0 (default), calls to malloc will behave normally.
27// If flag is 1, calls to malloc will behave like calls to new,
28// and the std_new_handler will be invoked on failure.
29// Can be set by calling _set_new_mode().
30static int new_mode = 0;
31
32typedef enum {
33  TCMALLOC,    // TCMalloc is the default allocator.
34  JEMALLOC,    // JEMalloc.
35  WINHEAP,  // Windows Heap (standard Windows allocator).
36  WINLFH,      // Windows LFH Heap.
37} Allocator;
38
39// This is the default allocator. This value can be changed at startup by
40// specifying environment variables shown below it.
41// See SetupSubprocessAllocator() to specify a default secondary (subprocess)
42// allocator.
43// TODO(jar): Switch to using TCMALLOC for the renderer as well.
44static Allocator allocator = WINHEAP;
45
46// The names of the environment variables that can optionally control the
47// selection of the allocator.  The primary may be used to control overall
48// allocator selection, and the secondary can be used to specify an allocator
49// to use in sub-processes.
50static const char* primary_name = "CHROME_ALLOCATOR";
51static const char* secondary_name = "CHROME_ALLOCATOR_2";
52
53// We include tcmalloc and the win_allocator to get as much inlining as
54// possible.
55#include "tcmalloc.cc"
56#include "win_allocator.cc"
57
58// Forward declarations from jemalloc.
59extern "C" {
60void* je_malloc(size_t s);
61void* je_realloc(void* p, size_t s);
62void je_free(void* s);
63size_t je_msize(void* p);
64bool je_malloc_init_hard();
65}
66
67extern "C" {
68
69// Call the new handler, if one has been set.
70// Returns true on successfully calling the handler, false otherwise.
71inline bool call_new_handler(bool nothrow) {
72  // Get the current new handler.  NB: this function is not
73  // thread-safe.  We make a feeble stab at making it so here, but
74  // this lock only protects against tcmalloc interfering with
75  // itself, not with other libraries calling set_new_handler.
76  std::new_handler nh;
77  {
78    SpinLockHolder h(&set_new_handler_lock);
79    nh = std::set_new_handler(0);
80    (void) std::set_new_handler(nh);
81  }
82#if (defined(__GNUC__) && !defined(__EXCEPTIONS)) || (defined(_HAS_EXCEPTIONS) && !_HAS_EXCEPTIONS)
83  if (!nh)
84    return false;
85  // Since exceptions are disabled, we don't really know if new_handler
86  // failed.  Assume it will abort if it fails.
87  (*nh)();
88  return false;  // break out of the retry loop.
89#else
90  // If no new_handler is established, the allocation failed.
91  if (!nh) {
92    if (nothrow)
93      return 0;
94    throw std::bad_alloc();
95  }
96  // Otherwise, try the new_handler.  If it returns, retry the
97  // allocation.  If it throws std::bad_alloc, fail the allocation.
98  // if it throws something else, don't interfere.
99  try {
100    (*nh)();
101  } catch (const std::bad_alloc&) {
102    if (!nothrow)
103      throw;
104    return true;
105  }
106#endif  // (defined(__GNUC__) && !defined(__EXCEPTIONS)) || (defined(_HAS_EXCEPTIONS) && !_HAS_EXCEPTIONS)
107}
108
109void* malloc(size_t size) __THROW {
110  void* ptr;
111  for (;;) {
112#ifdef ENABLE_DYNAMIC_ALLOCATOR_SWITCHING
113    switch (allocator) {
114      case JEMALLOC:
115        ptr = je_malloc(size);
116        break;
117      case WINHEAP:
118      case WINLFH:
119        ptr = win_heap_malloc(size);
120        break;
121      case TCMALLOC:
122      default:
123        ptr = do_malloc(size);
124        break;
125    }
126#else
127    // TCMalloc case.
128    ptr = do_malloc(size);
129#endif
130    if (ptr)
131      return ptr;
132
133    if (!new_mode || !call_new_handler(true))
134      break;
135  }
136  return ptr;
137}
138
139void free(void* p) __THROW {
140#ifdef ENABLE_DYNAMIC_ALLOCATOR_SWITCHING
141  switch (allocator) {
142    case JEMALLOC:
143      je_free(p);
144      return;
145    case WINHEAP:
146    case WINLFH:
147      win_heap_free(p);
148      return;
149  }
150#endif
151  // TCMalloc case.
152  do_free(p);
153}
154
155void* realloc(void* ptr, size_t size) __THROW {
156  // Webkit is brittle for allocators that return NULL for malloc(0).  The
157  // realloc(0, 0) code path does not guarantee a non-NULL return, so be sure
158  // to call malloc for this case.
159  if (!ptr)
160    return malloc(size);
161
162  void* new_ptr;
163  for (;;) {
164#ifdef ENABLE_DYNAMIC_ALLOCATOR_SWITCHING
165    switch (allocator) {
166      case JEMALLOC:
167        new_ptr = je_realloc(ptr, size);
168        break;
169      case WINHEAP:
170      case WINLFH:
171        new_ptr = win_heap_realloc(ptr, size);
172        break;
173      case TCMALLOC:
174      default:
175        new_ptr = do_realloc(ptr, size);
176        break;
177    }
178#else
179    // TCMalloc case.
180    new_ptr = do_realloc(ptr, size);
181#endif
182
183    // Subtle warning:  NULL return does not alwas indicate out-of-memory.  If
184    // the requested new size is zero, realloc should free the ptr and return
185    // NULL.
186    if (new_ptr || !size)
187      return new_ptr;
188    if (!new_mode || !call_new_handler(true))
189      break;
190  }
191  return new_ptr;
192}
193
194// TODO(mbelshe): Implement this for other allocators.
195void malloc_stats(void) __THROW {
196#ifdef ENABLE_DYNAMIC_ALLOCATOR_SWITCHING
197  switch (allocator) {
198    case JEMALLOC:
199      // No stats.
200      return;
201    case WINHEAP:
202    case WINLFH:
203      // No stats.
204      return;
205  }
206#endif
207  tc_malloc_stats();
208}
209
210#ifdef WIN32
211
212extern "C" size_t _msize(void* p) {
213#ifdef ENABLE_DYNAMIC_ALLOCATOR_SWITCHING
214  switch (allocator) {
215    case JEMALLOC:
216      return je_msize(p);
217    case WINHEAP:
218    case WINLFH:
219      return win_heap_msize(p);
220  }
221#endif
222  return MallocExtension::instance()->GetAllocatedSize(p);
223}
224
225// This is included to resolve references from libcmt.
226extern "C" intptr_t _get_heap_handle() {
227  return 0;
228}
229
230// The CRT heap initialization stub.
231extern "C" int _heap_init() {
232#ifdef ENABLE_DYNAMIC_ALLOCATOR_SWITCHING
233  const char* environment_value = GetenvBeforeMain(primary_name);
234  if (environment_value) {
235    if (!stricmp(environment_value, "jemalloc"))
236      allocator = JEMALLOC;
237    else if (!stricmp(environment_value, "winheap"))
238      allocator = WINHEAP;
239    else if (!stricmp(environment_value, "winlfh"))
240      allocator = WINLFH;
241    else if (!stricmp(environment_value, "tcmalloc"))
242      allocator = TCMALLOC;
243  }
244
245  switch (allocator) {
246    case JEMALLOC:
247      return je_malloc_init_hard() ? 0 : 1;
248    case WINHEAP:
249      return win_heap_init(false) ? 1 : 0;
250    case WINLFH:
251      return win_heap_init(true) ? 1 : 0;
252    case TCMALLOC:
253    default:
254      // fall through
255      break;
256  }
257#endif
258  // Initializing tcmalloc.
259  // We intentionally leak this object.  It lasts for the process
260  // lifetime.  Trying to teardown at _heap_term() is so late that
261  // you can't do anything useful anyway.
262  new TCMallocGuard();
263  return 1;
264}
265
266// The CRT heap cleanup stub.
267extern "C" void _heap_term() {}
268
269// We set this to 1 because part of the CRT uses a check of _crtheap != 0
270// to test whether the CRT has been initialized.  Once we've ripped out
271// the allocators from libcmt, we need to provide this definition so that
272// the rest of the CRT is still usable.
273extern "C" void* _crtheap = reinterpret_cast<void*>(1);
274
275#endif  // WIN32
276
277#include "generic_allocators.cc"
278
279}  // extern C
280
281namespace base {
282namespace allocator {
283
284void SetupSubprocessAllocator() {
285#ifdef ENABLE_DYNAMIC_ALLOCATOR_SWITCHING
286  size_t primary_length = 0;
287  getenv_s(&primary_length, NULL, 0, primary_name);
288
289  size_t secondary_length = 0;
290  char buffer[20];
291  getenv_s(&secondary_length, buffer, sizeof(buffer), secondary_name);
292  DCHECK_GT(sizeof(buffer), secondary_length);
293  buffer[sizeof(buffer) - 1] = '\0';
294
295  if (secondary_length || !primary_length) {
296    char* secondary_value = secondary_length ? buffer : "TCMALLOC";
297    // Force renderer (or other subprocesses) to use secondary_value.
298    int ret_val = _putenv_s(primary_name, secondary_value);
299    CHECK_EQ(0, ret_val);
300  }
301#endif  // ENABLE_DYNAMIC_ALLOCATOR_SWITCHING
302}
303
304}  // namespace base.
305}  // namespace allocator.
306