v2 / thirdparty / libatomic_ops / atomic_ops_malloc.c
428 lines · 374 sloc · 12.69 KB · f53b5d737fd636890520101e736ad29e20d3c322
Raw
1/*
2 * Copyright (c) 2005 Hewlett-Packard Development Company, L.P.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License along
15 * with this program; if not, write to the Free Software Foundation, Inc.,
16 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17 */
18
19#if defined(HAVE_CONFIG_H)
20# include "config.h"
21#endif
22
23#ifdef DONT_USE_MMAP /* for testing */
24# undef HAVE_MMAP
25#endif
26
27#ifndef AO_BUILD
28# define AO_BUILD
29#endif
30
31#define AO_REQUIRE_CAS
32#include "atomic_ops_malloc.h"
33
34#include <string.h> /* for ffs, which is assumed reentrant. */
35#include <stdlib.h>
36#include <assert.h>
37
38#ifdef AO_TRACE_MALLOC
39# include <stdio.h>
40# include <pthread.h>
41#endif
42
43#if defined(AO_ADDRESS_SANITIZER) && !defined(AO_NO_MALLOC_POISON)
44 /* #include "sanitizer/asan_interface.h" */
45 void __asan_poison_memory_region(void *, size_t);
46 void __asan_unpoison_memory_region(void *, size_t);
47# define ASAN_POISON_MEMORY_REGION(addr, size) \
48 __asan_poison_memory_region(addr, size)
49# define ASAN_UNPOISON_MEMORY_REGION(addr, size) \
50 __asan_unpoison_memory_region(addr, size)
51#else
52# define ASAN_POISON_MEMORY_REGION(addr, size) (void)0
53# define ASAN_UNPOISON_MEMORY_REGION(addr, size) (void)0
54#endif /* !AO_ADDRESS_SANITIZER */
55
56#if (defined(_WIN32_WCE) || defined(__MINGW32CE__)) && !defined(AO_HAVE_abort)
57# define abort() _exit(-1) /* there is no abort() in WinCE */
58#endif
59
60/*
61 * We round up each allocation request to the next power of two
62 * minus one word.
63 * We keep one stack of free objects for each size. Each object has
64 * an initial word (offset from the visible pointer is -sizeof(AO_uintptr_t))
65 * which contains either:
66 * - the binary log of the object size in bytes (for small objects), or
67 * - the object size (a multiple of CHUNK_SIZE) for large objects.
68 * The second case only arises if mmap-based allocation is supported.
69 * We align the user-visible part of each object on an ALIGNMENT
70 * byte boundary. That means that the actual (hidden) start of
71 * the object starts a word before this boundary.
72 */
73
74#ifndef LOG_MAX_SIZE
75# define LOG_MAX_SIZE 16
76 /* We assume that 2**LOG_MAX_SIZE is a multiple of page size. */
77#endif
78
79#ifndef ALIGNMENT
80# define ALIGNMENT 16
81 /* Assumed to be at least sizeof(AO_uintptr_t). */
82#endif
83
84#define CHUNK_SIZE (1 << LOG_MAX_SIZE)
85
86#ifndef AO_INITIAL_HEAP_SIZE
87# ifndef AO_INITIAL_HEAP_CHUNKS
88# define AO_INITIAL_HEAP_CHUNKS 2*(LOG_MAX_SIZE+1)
89# endif
90# define AO_INITIAL_HEAP_SIZE (AO_INITIAL_HEAP_CHUNKS * CHUNK_SIZE)
91#endif /* !AO_INITIAL_HEAP_SIZE */
92
93static char AO_initial_heap[AO_INITIAL_HEAP_SIZE]; /* ~2MB by default */
94
95static AO_internal_ptr_t volatile initial_heap_ptr = 0;
96
97#if defined(HAVE_MMAP)
98
99#include <sys/types.h>
100#include <sys/stat.h>
101#include <fcntl.h>
102#include <sys/mman.h>
103
104#if defined(MAP_ANONYMOUS) || defined(MAP_ANON)
105# define USE_MMAP_ANON
106#endif
107
108#ifdef USE_MMAP_FIXED
109# define GC_MMAP_FLAGS (MAP_FIXED | MAP_PRIVATE)
110 /* Seems to yield better performance on Solaris 2, but can */
111 /* be unreliable if something is already mapped at the address. */
112#else
113# define GC_MMAP_FLAGS MAP_PRIVATE
114#endif
115
116#ifdef USE_MMAP_ANON
117# if defined(CPPCHECK)
118# define OPT_MAP_ANON 0x20 /* taken from linux */
119# elif defined(MAP_ANONYMOUS)
120# define OPT_MAP_ANON MAP_ANONYMOUS
121# else
122# define OPT_MAP_ANON MAP_ANON
123# endif
124#else
125# include <unistd.h> /* for close() */
126# define OPT_MAP_ANON 0
127#endif
128
129static volatile AO_t mmap_enabled = 0;
130
131AO_API void
132AO_malloc_enable_mmap(void)
133{
134# if defined(__sun)
135 AO_store_release(&mmap_enabled, 1);
136 /* Workaround for Sun CC */
137# else
138 AO_store(&mmap_enabled, 1);
139# endif
140}
141
142static char *get_mmaped(size_t sz)
143{
144 char * result;
145# ifdef USE_MMAP_ANON
146# define zero_fd -1
147# else
148 int zero_fd;
149# endif
150
151 assert(!(sz & (CHUNK_SIZE - 1)));
152 if (!mmap_enabled)
153 return 0;
154
155# ifndef USE_MMAP_ANON
156 zero_fd = open("/dev/zero", O_RDONLY);
157 if (zero_fd == -1)
158 return 0;
159# endif
160 result = (char *)mmap(0, sz, PROT_READ | PROT_WRITE,
161 GC_MMAP_FLAGS | OPT_MAP_ANON,
162 zero_fd, 0 /* offset */);
163# ifndef USE_MMAP_ANON
164 close(zero_fd);
165# endif
166 if (AO_EXPECT_FALSE(result == MAP_FAILED))
167 result = NULL;
168 return result;
169}
170
171#ifndef SIZE_MAX
172# include <limits.h>
173#endif
174#if defined(SIZE_MAX) && !defined(CPPCHECK)
175# define AO_SIZE_MAX ((size_t)SIZE_MAX)
176 /* Extra cast to workaround some buggy SIZE_MAX definitions. */
177#else
178# define AO_SIZE_MAX (~(size_t)0)
179#endif
180
181/* Saturated addition of size_t values. Used to avoid value wrap */
182/* around on overflow. The arguments should have no side effects. */
183#define SIZET_SAT_ADD(a, b) \
184 (AO_EXPECT_FALSE((a) >= AO_SIZE_MAX - (b)) ? AO_SIZE_MAX : (a) + (b))
185
186/* Allocate an object of size (incl. header) of size > CHUNK_SIZE. */
187/* sz includes space for a pointer-sized header. */
188static char *
189AO_malloc_large(size_t sz)
190{
191 void *result;
192
193 /* The header will force us to waste ALIGNMENT bytes, including the */
194 /* header. Round to multiple of CHUNK_SIZE. */
195 sz = SIZET_SAT_ADD(sz, ALIGNMENT + CHUNK_SIZE - 1)
196 & ~(size_t)(CHUNK_SIZE - 1);
197 assert(sz > LOG_MAX_SIZE);
198 result = get_mmaped(sz);
199 if (AO_EXPECT_FALSE(NULL == result))
200 return NULL;
201
202 result = (AO_uintptr_t *)result + ALIGNMENT / sizeof(AO_uintptr_t);
203 ((AO_uintptr_t *)result)[-1] = (AO_uintptr_t)sz;
204 return (char *)result;
205}
206
207static void
208AO_free_large(void *p)
209{
210 size_t sz = (size_t)(((AO_uintptr_t *)p)[-1]);
211
212 if (munmap((AO_uintptr_t *)p - ALIGNMENT / sizeof(AO_uintptr_t), sz) != 0)
213 abort(); /* Programmer error. Not really async-signal-safe, but ... */
214}
215
216#else /* !HAVE_MMAP */
217
218AO_API void
219AO_malloc_enable_mmap(void)
220{
221}
222
223#define get_mmaped(sz) ((char*)0)
224#define AO_malloc_large(sz) ((char*)0)
225#define AO_free_large(p) abort()
226 /* Programmer error. Not really async-signal-safe, but ... */
227
228#endif /* !HAVE_MMAP */
229
230/* TODO: Duplicates (partially) the definitions in atomic_ops_stack.c. */
231#if defined(AO_FAT_POINTER) || defined(AO_STACK_USE_CPTR)
232 AO_INLINE int
233 AO_cptr_compare_and_swap(AO_internal_ptr_t volatile *addr,
234 AO_internal_ptr_t old_val, AO_internal_ptr_t new_val)
235 {
236 return (int)__atomic_compare_exchange_n(addr, &old_val, new_val, 0,
237 __ATOMIC_RELAXED, __ATOMIC_RELAXED);
238 }
239
240 AO_INLINE int
241 AO_cptr_compare_and_swap_acquire(AO_internal_ptr_t volatile *addr,
242 AO_internal_ptr_t old_val, AO_internal_ptr_t new_val)
243 {
244 return (int)__atomic_compare_exchange_n(addr, &old_val, new_val, 0,
245 __ATOMIC_ACQUIRE, __ATOMIC_ACQUIRE);
246 }
247
248# define AO_cptr_load(p) __atomic_load_n(p, __ATOMIC_RELAXED)
249#else
250# define AO_cptr_compare_and_swap AO_compare_and_swap
251# define AO_cptr_compare_and_swap_acquire AO_compare_and_swap_acquire
252# define AO_cptr_load AO_load
253#endif
254
255static char *
256get_chunk(void)
257{
258 AO_internal_ptr_t my_chunk_ptr;
259
260 for (;;) {
261 AO_internal_ptr_t initial_ptr = AO_cptr_load(&initial_heap_ptr);
262
263 my_chunk_ptr = AO_EXPECT_FALSE(0 == initial_ptr) ?
264 (AO_internal_ptr_t)AO_initial_heap : initial_ptr;
265 /* Round up the pointer to ALIGNMENT. */
266# ifdef AO_STACK_USE_CPTR
267 my_chunk_ptr += ((size_t)ALIGNMENT - (size_t)(AO_uintptr_t)my_chunk_ptr)
268 & (ALIGNMENT - 1);
269# else
270 my_chunk_ptr = (AO_internal_ptr_t)(((AO_uintptr_t)my_chunk_ptr
271 + ALIGNMENT-1)
272 & ~(AO_uintptr_t)(ALIGNMENT-1));
273# endif
274 if (initial_ptr != my_chunk_ptr) {
275 /* Align correctly. If this fails, someone else did it for us. */
276 assert(my_chunk_ptr != 0);
277 (void)AO_cptr_compare_and_swap_acquire(&initial_heap_ptr, initial_ptr,
278 my_chunk_ptr);
279 }
280
281 if (AO_EXPECT_FALSE((AO_uintptr_t)my_chunk_ptr
282 < (AO_uintptr_t)AO_initial_heap)
283 || AO_EXPECT_FALSE((AO_uintptr_t)my_chunk_ptr
284 > (AO_uintptr_t)(AO_initial_heap
285 + AO_INITIAL_HEAP_SIZE - CHUNK_SIZE))) {
286 /* We failed. The initial heap is used up. */
287 my_chunk_ptr = (AO_internal_ptr_t)get_mmaped(CHUNK_SIZE);
288# if !defined(CPPCHECK)
289 assert(((AO_uintptr_t)my_chunk_ptr & (ALIGNMENT - 1)) == 0);
290# endif
291 break;
292 }
293 if (AO_cptr_compare_and_swap(&initial_heap_ptr, my_chunk_ptr,
294 my_chunk_ptr + CHUNK_SIZE)) {
295 break;
296 }
297 }
298 return (char *)my_chunk_ptr;
299}
300
301/* Object free lists. I-th entry corresponds to objects */
302/* of total size 2**i bytes. */
303static AO_stack_t AO_free_list[LOG_MAX_SIZE+1];
304
305/* Break up the chunk, and add it to the object free list for */
306/* the given size. We have exclusive access to chunk. */
307static void add_chunk_as(void * chunk, unsigned log_sz)
308{
309 size_t ofs, limit;
310 size_t sz = (size_t)1 << log_sz;
311
312 assert((size_t)CHUNK_SIZE >= sz);
313 assert(sz % sizeof(AO_uintptr_t) == 0);
314 limit = (size_t)CHUNK_SIZE - sz;
315 for (ofs = ALIGNMENT - sizeof(AO_uintptr_t); ofs <= limit; ofs += sz) {
316 ASAN_POISON_MEMORY_REGION((char *)chunk + ofs + sizeof(AO_uintptr_t),
317 sz - sizeof(AO_uintptr_t));
318 AO_stack_push(&AO_free_list[log_sz],
319 (AO_uintptr_t *)chunk + ofs / sizeof(AO_uintptr_t));
320 }
321}
322
323static const unsigned char msbs[16] = {
324 0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4
325};
326
327/* Return the position of the most significant set bit in the */
328/* argument. */
329/* We follow the conventions of ffs(), i.e. the least */
330/* significant bit is number one. */
331static unsigned msb(size_t s)
332{
333 unsigned result = 0;
334 if ((s & 0xff) != s) {
335# if (__SIZEOF_SIZE_T__ == 8) && !defined(CPPCHECK)
336 unsigned v = (unsigned)(s >> 32);
337 if (AO_EXPECT_FALSE(v != 0))
338 {
339 s = v;
340 result += 32;
341 }
342# elif __SIZEOF_SIZE_T__ == 4
343 /* No op. */
344# else
345 unsigned v;
346 /* The following is a tricky code ought to be equivalent to */
347 /* "(v = s >> 32) != 0" but suppresses warnings on 32-bit arch's. */
348# define SIZEOF_SIZE_T_GT_4 (sizeof(size_t) > 4)
349 if (SIZEOF_SIZE_T_GT_4
350 && (v = (unsigned)(s >> (SIZEOF_SIZE_T_GT_4 ? 32 : 0))) != 0)
351 {
352 s = v;
353 result += 32;
354 }
355# endif /* !defined(__SIZEOF_SIZE_T__) */
356 if (AO_EXPECT_FALSE((s >> 16) != 0))
357 {
358 s >>= 16;
359 result += 16;
360 }
361 if ((s >> 8) != 0)
362 {
363 s >>= 8;
364 result += 8;
365 }
366 }
367 if (s > 15)
368 {
369 s >>= 4;
370 result += 4;
371 }
372 result += msbs[s];
373 return result;
374}
375
376AO_API AO_ATTR_MALLOC AO_ATTR_ALLOC_SIZE(1)
377void *
378AO_malloc(size_t sz)
379{
380 AO_uintptr_t *result;
381 unsigned log_sz;
382
383 if (AO_EXPECT_FALSE(sz > CHUNK_SIZE - sizeof(AO_uintptr_t)))
384 return AO_malloc_large(sz);
385 log_sz = msb(sz + sizeof(AO_uintptr_t) - 1);
386 assert(log_sz <= LOG_MAX_SIZE);
387 assert(((size_t)1 << log_sz) >= sz + sizeof(AO_uintptr_t));
388 result = AO_stack_pop(AO_free_list + log_sz);
389 while (AO_EXPECT_FALSE(NULL == result)) {
390 void *chunk = get_chunk();
391
392 if (AO_EXPECT_FALSE(NULL == chunk))
393 return NULL;
394 add_chunk_as(chunk, log_sz);
395 result = AO_stack_pop(AO_free_list + log_sz);
396 }
397 *result = log_sz;
398# ifdef AO_TRACE_MALLOC
399 fprintf(stderr, "%p: AO_malloc(%lu) = %p\n",
400 (void *)pthread_self(), (unsigned long)sz, (void *)(result + 1));
401# endif
402 ASAN_UNPOISON_MEMORY_REGION(result + 1, sz);
403 return result + 1;
404}
405
406AO_API void
407AO_free(void *p)
408{
409 AO_uintptr_t *base;
410 int log_sz;
411
412 if (AO_EXPECT_FALSE(NULL == p))
413 return;
414
415 base = (AO_uintptr_t *)p - 1;
416 log_sz = (int)(*base);
417# ifdef AO_TRACE_MALLOC
418 fprintf(stderr, "%p: AO_free(%p sz:%lu)\n", (void *)pthread_self(), p,
419 log_sz > LOG_MAX_SIZE ? (unsigned)log_sz : 1UL << log_sz);
420# endif
421 if (AO_EXPECT_FALSE(log_sz > LOG_MAX_SIZE)) {
422 AO_free_large(p);
423 } else {
424 ASAN_POISON_MEMORY_REGION(base + 1,
425 ((size_t)1 << log_sz) - sizeof(AO_uintptr_t));
426 AO_stack_push(AO_free_list + log_sz, base);
427 }
428}
429