1// Copyright (c) 2005, 2007, Google Inc.
2// All rights reserved.
3//
4// Redistribution and use in source and binary forms, with or without
5// modification, are permitted provided that the following conditions are
6// met:
7//
8//     * Redistributions of source code must retain the above copyright
9// notice, this list of conditions and the following disclaimer.
10//     * Redistributions in binary form must reproduce the above
11// copyright notice, this list of conditions and the following disclaimer
12// in the documentation and/or other materials provided with the
13// distribution.
14//     * Neither the name of Google Inc. nor the names of its
15// contributors may be used to endorse or promote products derived from
16// this software without specific prior written permission.
17//
18// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
30// ---
31// Author: Sanjay Ghemawat
32
33#include "config.h"
34#if !(defined(USE_SYSTEM_MALLOC) && USE_SYSTEM_MALLOC)
35#include "TCSystemAlloc.h"
36
37#include <algorithm>
38#include "Assertions.h"
39#include "TCSpinLock.h"
40#include "UnusedParam.h"
41#include "VMTags.h"
42
43#if HAVE(STDINT_H)
44#include <stdint.h>
45#elif HAVE(INTTYPES_H)
46#include <inttypes.h>
47#else
48#include <sys/types.h>
49#endif
50
51#if OS(WINDOWS)
52#include "windows.h"
53#else
54#include <errno.h>
55#include <unistd.h>
56#include <sys/mman.h>
57#endif
58
59#ifndef MAP_ANONYMOUS
60#define MAP_ANONYMOUS MAP_ANON
61#endif
62
63using namespace std;
64
65// Structure for discovering alignment
66union MemoryAligner {
67  void*  p;
68  double d;
69  size_t s;
70};
71
72static SpinLock spinlock = SPINLOCK_INITIALIZER;
73
74// Page size is initialized on demand
75static size_t pagesize = 0;
76
77// Configuration parameters.
78//
79// if use_devmem is true, either use_sbrk or use_mmap must also be true.
80// For 2.2 kernels, it looks like the sbrk address space (500MBish) and
81// the mmap address space (1300MBish) are disjoint, so we need both allocators
82// to get as much virtual memory as possible.
83#ifndef WTF_CHANGES
84static bool use_devmem = false;
85#endif
86
87#if HAVE(SBRK)
88static bool use_sbrk = false;
89#endif
90
91#if HAVE(MMAP)
92static bool use_mmap = true;
93#endif
94
95#if HAVE(VIRTUALALLOC)
96static bool use_VirtualAlloc = true;
97#endif
98
99// Flags to keep us from retrying allocators that failed.
100static bool devmem_failure = false;
101static bool sbrk_failure = false;
102static bool mmap_failure = false;
103static bool VirtualAlloc_failure = false;
104
105#ifndef WTF_CHANGES
106DEFINE_int32(malloc_devmem_start, 0,
107             "Physical memory starting location in MB for /dev/mem allocation."
108             "  Setting this to 0 disables /dev/mem allocation");
109DEFINE_int32(malloc_devmem_limit, 0,
110             "Physical memory limit location in MB for /dev/mem allocation."
111             "  Setting this to 0 means no limit.");
112#else
113static const int32_t FLAGS_malloc_devmem_start = 0;
114static const int32_t FLAGS_malloc_devmem_limit = 0;
115#endif
116
117#if HAVE(SBRK)
118
119static void* TrySbrk(size_t size, size_t *actual_size, size_t alignment) {
120  size = ((size + alignment - 1) / alignment) * alignment;
121
122  // could theoretically return the "extra" bytes here, but this
123  // is simple and correct.
124  if (actual_size)
125    *actual_size = size;
126
127  void* result = sbrk(size);
128  if (result == reinterpret_cast<void*>(-1)) {
129    sbrk_failure = true;
130    return NULL;
131  }
132
133  // Is it aligned?
134  uintptr_t ptr = reinterpret_cast<uintptr_t>(result);
135  if ((ptr & (alignment-1)) == 0)  return result;
136
137  // Try to get more memory for alignment
138  size_t extra = alignment - (ptr & (alignment-1));
139  void* r2 = sbrk(extra);
140  if (reinterpret_cast<uintptr_t>(r2) == (ptr + size)) {
141    // Contiguous with previous result
142    return reinterpret_cast<void*>(ptr + extra);
143  }
144
145  // Give up and ask for "size + alignment - 1" bytes so
146  // that we can find an aligned region within it.
147  result = sbrk(size + alignment - 1);
148  if (result == reinterpret_cast<void*>(-1)) {
149    sbrk_failure = true;
150    return NULL;
151  }
152  ptr = reinterpret_cast<uintptr_t>(result);
153  if ((ptr & (alignment-1)) != 0) {
154    ptr += alignment - (ptr & (alignment-1));
155  }
156  return reinterpret_cast<void*>(ptr);
157}
158
159#endif /* HAVE(SBRK) */
160
161#if HAVE(MMAP)
162
163static void* TryMmap(size_t size, size_t *actual_size, size_t alignment) {
164  // Enforce page alignment
165  if (pagesize == 0) pagesize = getpagesize();
166  if (alignment < pagesize) alignment = pagesize;
167  size = ((size + alignment - 1) / alignment) * alignment;
168
169  // could theoretically return the "extra" bytes here, but this
170  // is simple and correct.
171  if (actual_size)
172    *actual_size = size;
173
174  // Ask for extra memory if alignment > pagesize
175  size_t extra = 0;
176  if (alignment > pagesize) {
177    extra = alignment - pagesize;
178  }
179  void* result = mmap(NULL, size + extra,
180                      PROT_READ | PROT_WRITE,
181                      MAP_PRIVATE|MAP_ANONYMOUS,
182                      VM_TAG_FOR_TCMALLOC_MEMORY, 0);
183  if (result == reinterpret_cast<void*>(MAP_FAILED)) {
184    mmap_failure = true;
185    return NULL;
186  }
187
188  // Adjust the return memory so it is aligned
189  uintptr_t ptr = reinterpret_cast<uintptr_t>(result);
190  size_t adjust = 0;
191  if ((ptr & (alignment - 1)) != 0) {
192    adjust = alignment - (ptr & (alignment - 1));
193  }
194
195  // Return the unused memory to the system
196  if (adjust > 0) {
197    munmap(reinterpret_cast<void*>(ptr), adjust);
198  }
199  if (adjust < extra) {
200    munmap(reinterpret_cast<void*>(ptr + adjust + size), extra - adjust);
201  }
202
203  ptr += adjust;
204  return reinterpret_cast<void*>(ptr);
205}
206
207#endif /* HAVE(MMAP) */
208
209#if HAVE(VIRTUALALLOC)
210
211static void* TryVirtualAlloc(size_t size, size_t *actual_size, size_t alignment) {
212  // Enforce page alignment
213  if (pagesize == 0) {
214    SYSTEM_INFO system_info;
215    GetSystemInfo(&system_info);
216    pagesize = system_info.dwPageSize;
217  }
218
219  if (alignment < pagesize) alignment = pagesize;
220  size = ((size + alignment - 1) / alignment) * alignment;
221
222  // could theoretically return the "extra" bytes here, but this
223  // is simple and correct.
224  if (actual_size)
225    *actual_size = size;
226
227  // Ask for extra memory if alignment > pagesize
228  size_t extra = 0;
229  if (alignment > pagesize) {
230    extra = alignment - pagesize;
231  }
232  void* result = VirtualAlloc(NULL, size + extra,
233                              MEM_RESERVE | MEM_COMMIT | MEM_TOP_DOWN,
234                              PAGE_READWRITE);
235
236  if (result == NULL) {
237    VirtualAlloc_failure = true;
238    return NULL;
239  }
240
241  // Adjust the return memory so it is aligned
242  uintptr_t ptr = reinterpret_cast<uintptr_t>(result);
243  size_t adjust = 0;
244  if ((ptr & (alignment - 1)) != 0) {
245    adjust = alignment - (ptr & (alignment - 1));
246  }
247
248  // Return the unused memory to the system - we'd like to release but the best we can do
249  // is decommit, since Windows only lets you free the whole allocation.
250  if (adjust > 0) {
251    VirtualFree(reinterpret_cast<void*>(ptr), adjust, MEM_DECOMMIT);
252  }
253  if (adjust < extra) {
254    VirtualFree(reinterpret_cast<void*>(ptr + adjust + size), extra-adjust, MEM_DECOMMIT);
255  }
256
257  ptr += adjust;
258  return reinterpret_cast<void*>(ptr);
259}
260
261#endif /* HAVE(MMAP) */
262
263#ifndef WTF_CHANGES
264static void* TryDevMem(size_t size, size_t *actual_size, size_t alignment) {
265  static bool initialized = false;
266  static off_t physmem_base;  // next physical memory address to allocate
267  static off_t physmem_limit; // maximum physical address allowed
268  static int physmem_fd;      // file descriptor for /dev/mem
269
270  // Check if we should use /dev/mem allocation.  Note that it may take
271  // a while to get this flag initialized, so meanwhile we fall back to
272  // the next allocator.  (It looks like 7MB gets allocated before
273  // this flag gets initialized -khr.)
274  if (FLAGS_malloc_devmem_start == 0) {
275    // NOTE: not a devmem_failure - we'd like TCMalloc_SystemAlloc to
276    // try us again next time.
277    return NULL;
278  }
279
280  if (!initialized) {
281    physmem_fd = open("/dev/mem", O_RDWR);
282    if (physmem_fd < 0) {
283      devmem_failure = true;
284      return NULL;
285    }
286    physmem_base = FLAGS_malloc_devmem_start*1024LL*1024LL;
287    physmem_limit = FLAGS_malloc_devmem_limit*1024LL*1024LL;
288    initialized = true;
289  }
290
291  // Enforce page alignment
292  if (pagesize == 0) pagesize = getpagesize();
293  if (alignment < pagesize) alignment = pagesize;
294  size = ((size + alignment - 1) / alignment) * alignment;
295
296  // could theoretically return the "extra" bytes here, but this
297  // is simple and correct.
298  if (actual_size)
299    *actual_size = size;
300
301  // Ask for extra memory if alignment > pagesize
302  size_t extra = 0;
303  if (alignment > pagesize) {
304    extra = alignment - pagesize;
305  }
306
307  // check to see if we have any memory left
308  if (physmem_limit != 0 && physmem_base + size + extra > physmem_limit) {
309    devmem_failure = true;
310    return NULL;
311  }
312  void *result = mmap(0, size + extra, PROT_READ | PROT_WRITE,
313                      MAP_SHARED, physmem_fd, physmem_base);
314  if (result == reinterpret_cast<void*>(MAP_FAILED)) {
315    devmem_failure = true;
316    return NULL;
317  }
318  uintptr_t ptr = reinterpret_cast<uintptr_t>(result);
319
320  // Adjust the return memory so it is aligned
321  size_t adjust = 0;
322  if ((ptr & (alignment - 1)) != 0) {
323    adjust = alignment - (ptr & (alignment - 1));
324  }
325
326  // Return the unused virtual memory to the system
327  if (adjust > 0) {
328    munmap(reinterpret_cast<void*>(ptr), adjust);
329  }
330  if (adjust < extra) {
331    munmap(reinterpret_cast<void*>(ptr + adjust + size), extra - adjust);
332  }
333
334  ptr += adjust;
335  physmem_base += adjust + size;
336
337  return reinterpret_cast<void*>(ptr);
338}
339#endif
340
341void* TCMalloc_SystemAlloc(size_t size, size_t *actual_size, size_t alignment) {
342  // Discard requests that overflow
343  if (size + alignment < size) return NULL;
344
345  SpinLockHolder lock_holder(&spinlock);
346
347  // Enforce minimum alignment
348  if (alignment < sizeof(MemoryAligner)) alignment = sizeof(MemoryAligner);
349
350  // Try twice, once avoiding allocators that failed before, and once
351  // more trying all allocators even if they failed before.
352  for (int i = 0; i < 2; i++) {
353
354#ifndef WTF_CHANGES
355    if (use_devmem && !devmem_failure) {
356      void* result = TryDevMem(size, actual_size, alignment);
357      if (result != NULL) return result;
358    }
359#endif
360
361#if HAVE(SBRK)
362    if (use_sbrk && !sbrk_failure) {
363      void* result = TrySbrk(size, actual_size, alignment);
364      if (result != NULL) return result;
365    }
366#endif
367
368#if HAVE(MMAP)
369    if (use_mmap && !mmap_failure) {
370      void* result = TryMmap(size, actual_size, alignment);
371      if (result != NULL) return result;
372    }
373#endif
374
375#if HAVE(VIRTUALALLOC)
376    if (use_VirtualAlloc && !VirtualAlloc_failure) {
377      void* result = TryVirtualAlloc(size, actual_size, alignment);
378      if (result != NULL) return result;
379    }
380#endif
381
382    // nothing worked - reset failure flags and try again
383    devmem_failure = false;
384    sbrk_failure = false;
385    mmap_failure = false;
386    VirtualAlloc_failure = false;
387  }
388  return NULL;
389}
390
391#if HAVE(MADV_FREE_REUSE)
392
393void TCMalloc_SystemRelease(void* start, size_t length)
394{
395    while (madvise(start, length, MADV_FREE_REUSABLE) == -1 && errno == EAGAIN) { }
396}
397
398#elif HAVE(MADV_FREE) || HAVE(MADV_DONTNEED)
399
400void TCMalloc_SystemRelease(void* start, size_t length)
401{
402    // MADV_FREE clears the modified bit on pages, which allows
403    // them to be discarded immediately.
404#if HAVE(MADV_FREE)
405    const int advice = MADV_FREE;
406#else
407    const int advice = MADV_DONTNEED;
408#endif
409  if (FLAGS_malloc_devmem_start) {
410    // It's not safe to use MADV_DONTNEED if we've been mapping
411    // /dev/mem for heap memory
412    return;
413  }
414  if (pagesize == 0) pagesize = getpagesize();
415  const size_t pagemask = pagesize - 1;
416
417  size_t new_start = reinterpret_cast<size_t>(start);
418  size_t end = new_start + length;
419  size_t new_end = end;
420
421  // Round up the starting address and round down the ending address
422  // to be page aligned:
423  new_start = (new_start + pagesize - 1) & ~pagemask;
424  new_end = new_end & ~pagemask;
425
426  ASSERT((new_start & pagemask) == 0);
427  ASSERT((new_end & pagemask) == 0);
428  ASSERT(new_start >= reinterpret_cast<size_t>(start));
429  ASSERT(new_end <= end);
430
431  if (new_end > new_start) {
432    // Note -- ignoring most return codes, because if this fails it
433    // doesn't matter...
434    while (madvise(reinterpret_cast<char*>(new_start), new_end - new_start,
435                   advice) == -1 &&
436           errno == EAGAIN) {
437      // NOP
438    }
439  }
440}
441
442#elif HAVE(MMAP)
443
444void TCMalloc_SystemRelease(void* start, size_t length)
445{
446  void* newAddress = mmap(start, length, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0);
447  // If the mmap failed then that's ok, we just won't return the memory to the system.
448  ASSERT_UNUSED(newAddress, newAddress == start || newAddress == reinterpret_cast<void*>(MAP_FAILED));
449}
450
451#elif HAVE(VIRTUALALLOC)
452
453void TCMalloc_SystemRelease(void* start, size_t length)
454{
455    if (VirtualFree(start, length, MEM_DECOMMIT))
456        return;
457
458    // The decommit may fail if the memory region consists of allocations
459    // from more than one call to VirtualAlloc.  In this case, fall back to
460    // using VirtualQuery to retrieve the allocation boundaries and decommit
461    // them each individually.
462
463    char* ptr = static_cast<char*>(start);
464    char* end = ptr + length;
465    MEMORY_BASIC_INFORMATION info;
466    while (ptr < end) {
467        size_t resultSize = VirtualQuery(ptr, &info, sizeof(info));
468        ASSERT_UNUSED(resultSize, resultSize == sizeof(info));
469
470        size_t decommitSize = min<size_t>(info.RegionSize, end - ptr);
471        BOOL success = VirtualFree(ptr, decommitSize, MEM_DECOMMIT);
472        ASSERT_UNUSED(success, success);
473        ptr += decommitSize;
474    }
475}
476
477#else
478
479// Platforms that don't support returning memory use an empty inline version of TCMalloc_SystemRelease
480// declared in TCSystemAlloc.h
481
482#endif
483
484#if HAVE(MADV_FREE_REUSE)
485
486void TCMalloc_SystemCommit(void* start, size_t length)
487{
488    while (madvise(start, length, MADV_FREE_REUSE) == -1 && errno == EAGAIN) { }
489}
490
491#elif HAVE(VIRTUALALLOC)
492
493void TCMalloc_SystemCommit(void* start, size_t length)
494{
495    if (VirtualAlloc(start, length, MEM_COMMIT, PAGE_READWRITE) == start)
496        return;
497
498    // The commit may fail if the memory region consists of allocations
499    // from more than one call to VirtualAlloc.  In this case, fall back to
500    // using VirtualQuery to retrieve the allocation boundaries and commit them
501    // each individually.
502
503    char* ptr = static_cast<char*>(start);
504    char* end = ptr + length;
505    MEMORY_BASIC_INFORMATION info;
506    while (ptr < end) {
507        size_t resultSize = VirtualQuery(ptr, &info, sizeof(info));
508        ASSERT_UNUSED(resultSize, resultSize == sizeof(info));
509
510        size_t commitSize = min<size_t>(info.RegionSize, end - ptr);
511        void* newAddress = VirtualAlloc(ptr, commitSize, MEM_COMMIT, PAGE_READWRITE);
512        ASSERT_UNUSED(newAddress, newAddress == ptr);
513        ptr += commitSize;
514    }
515}
516
517#else
518
519// Platforms that don't need to explicitly commit memory use an empty inline version of TCMalloc_SystemCommit
520// declared in TCSystemAlloc.h
521
522#endif
523
524#endif // #if !(defined(USE_SYSTEM_MALLOC) && USE_SYSTEM_MALLOC)
525
526