]> git.saurik.com Git - apple/libc.git/blob - gen/magazine_malloc.c
a1fb6f000f51b5c4ef651abd1504aa09658a445a
[apple/libc.git] / gen / magazine_malloc.c
1 /*
2 * Copyright (c) 1999, 2006, 2008 Apple Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24 /* Author: Bertrand Serlet, August 1999 */
25
26 /*
27 Multithread enhancements for "tiny" allocations introduced February 2008.
28 These are in the spirit of "Hoard". See:
29 Berger, E.D.; McKinley, K.S.; Blumofe, R.D.; Wilson, P.R. (2000).
30 "Hoard: a scalable memory allocator for multithreaded applications".
31 ACM SIGPLAN Notices 35 (11): 117-128. Berger2000.
32 <http://portal.acm.org/citation.cfm?id=356989.357000>
33 Retrieved on 2008-02-22.
34 */
35
36 /* gcc -g -O3 magazine_malloc.c malloc.c -o libmagmalloc.dylib -I. \
37 -I/System/Library/Frameworks/System.framework/PrivateHeaders/ -funit-at-a-time \
38 -dynamiclib -Wall -arch x86_64 -arch i386 -arch ppc */
39
40 #include "scalable_malloc.h"
41 #include "malloc_printf.h"
42 #include "_simple.h"
43 #include "magmallocProvider.h"
44
45 #include <pthread_internals.h> /* for pthread_lock_t SPI */
46 #include <pthread.h> /* for pthread API */
47
48 #include <stdint.h>
49 #include <unistd.h>
50 #include <mach/vm_statistics.h>
51 #include <mach/mach_init.h>
52 #include <sys/types.h>
53 #include <sys/mman.h>
54 #include <sys/sysctl.h>
55 #include <libkern/OSAtomic.h>
56 #include <mach-o/dyld.h> /* for NSVersionOfLinkTimeLibrary() */
57
58 /********************* DEFINITIONS ************************/
59
60 #define DEBUG_MALLOC 0 // set to one to debug malloc itself
61
62 #define DEBUG_CLIENT 0 // set to one to debug malloc client
63
64 #if DEBUG_MALLOC
65 #warning DEBUG_MALLOC ENABLED
66 # define INLINE
67 # define ALWAYSINLINE
68 # define CHECK_MAGAZINE_PTR_LOCKED(szone, mag_ptr, fun) \
69 do { \
70 if (__is_threaded && TRY_LOCK(mag_ptr->magazine_lock)) { \
71 malloc_printf("*** magazine_lock was not set %p in %s\n", \
72 mag_ptr->magazine_lock, fun); \
73 } \
74 } while (0)
75 #else
76 # define INLINE __inline__
77 # define ALWAYSINLINE __attribute__((always_inline))
78 # define CHECK_MAGAZINE_PTR_LOCKED(szone, mag_ptr, fun) {}
79 #endif
80
81 # define NOINLINE __attribute__((noinline))
82
83 #if defined(__i386__) || defined(__x86_64__)
84 #define CACHE_ALIGN __attribute__ ((aligned (128) )) /* Future-proofing at 128B */
85 #elif defined(__ppc__) || defined(__ppc64__)
86 #define CACHE_ALIGN __attribute__ ((aligned (128) ))
87 #else
88 #define CACHE_ALIGN
89 #endif
90
91 /*
92 * Access to global variables is slow, so optimise our handling of vm_page_size
93 * and vm_page_shift.
94 */
95 #define _vm_page_size vm_page_size /* to get to the originals */
96 #define _vm_page_shift vm_page_shift
97 #define vm_page_size 4096 /* our normal working sizes */
98 #define vm_page_shift 12
99
100 /*
101 * msize - a type to refer to the number of quanta of a tiny or small
102 * allocation. A tiny block with an msize of 3 would be 3 << SHIFT_TINY_QUANTUM
103 * bytes in size.
104 */
105 typedef unsigned short msize_t;
106
107 typedef union {
108 void *p;
109 uintptr_t u;
110 } ptr_union;
111
112 typedef struct {
113 ptr_union previous;
114 ptr_union next;
115 } free_list_t;
116
117 typedef unsigned int grain_t; // N.B. wide enough to index all free slots
118
119 typedef int mag_index_t;
120
121 #define CHECK_REGIONS (1 << 31)
122
123 #define MAX_RECORDER_BUFFER 256
124
125 /********************* DEFINITIONS for tiny ************************/
126
127 /*
128 * Memory in the Tiny range is allocated from regions (heaps) pointed to by the
129 * szone's hashed_regions pointer.
130 *
131 * Each region is laid out as a heap, followed by a header block, all within
132 * a 1MB (2^20) block. This means there are 64520 16-byte blocks and the header
133 * is 16138 bytes, making the total 1048458 bytes, leaving 118 bytes unused.
134 *
135 * The header block is arranged as in struct tiny_region defined just below, and
136 * consists of two bitfields (or bit arrays) interleaved 32 bits by 32 bits.
137 *
138 * Each bitfield comprises NUM_TINY_BLOCKS bits, and refers to the corresponding
139 * TINY_QUANTUM block within the heap.
140 *
141 * The bitfields are used to encode the state of memory within the heap. The header bit indicates
142 * that the corresponding quantum is the first quantum in a block (either in use or free). The
143 * in-use bit is set for the header if the block has been handed out (allocated). If the header
144 * bit is not set, the in-use bit is invalid.
145 *
146 * The szone maintains an array of NUM_TINY_SLOTS freelists, each of which is used to hold
147 * free objects of the corresponding quantum size.
148 *
149 * A free block is laid out depending on its size, in order to fit all free
150 * blocks in 16 bytes, on both 32 and 64 bit platforms. One quantum blocks do
151 * not store their size in the block, instead relying on the header information
152 * to determine their size. Blocks of two or more quanta have room to store
153 * their size in the block, and store it both after the 'next' pointer, and in
154 * the last 2 bytes of the block.
155 *
156 * 1-quantum block
157 * Offset (32-bit mode) (64-bit mode)
158 * 0x0 0x0 : previous
159 * 0x4 0x08 : next
160 * end end
161 *
162 * >1-quantum block
163 * Offset (32-bit mode) (64-bit mode)
164 * 0x0 0x0 : previous
165 * 0x4 0x08 : next
166 * 0x8 0x10 : size (in quantum counts)
167 * end - 2 end - 2 : size (in quantum counts)
168 * end end
169 *
170 * All fields are pointer-sized, except for the size which is an unsigned short.
171 *
172 */
173
174 #define SHIFT_TINY_QUANTUM 4 // Required for AltiVec
175 #define TINY_QUANTUM (1 << SHIFT_TINY_QUANTUM)
176
177 #define FOLLOWING_TINY_PTR(ptr,msize) (((unsigned char *)(ptr)) + ((msize) << SHIFT_TINY_QUANTUM))
178
179 #ifdef __LP64__
180 #define NUM_TINY_SLOTS 64 // number of slots for free-lists
181 #else
182 #define NUM_TINY_SLOTS 32 // number of slots for free-lists
183 #endif
184
185 #define NUM_TINY_BLOCKS 64520
186 #define SHIFT_TINY_CEIL_BLOCKS 16 // ceil(log2(NUM_TINY_BLOCKS))
187 #define NUM_TINY_CEIL_BLOCKS (1 << SHIFT_TINY_CEIL_BLOCKS)
188 #define TINY_BLOCKS_ALIGN (SHIFT_TINY_CEIL_BLOCKS + SHIFT_TINY_QUANTUM) // 20
189
190 /*
191 * Enough room for the data, followed by the bit arrays (2-bits per block)
192 * plus rounding to the nearest page.
193 */
194 #define CEIL_NUM_TINY_BLOCKS_WORDS (((NUM_TINY_BLOCKS + 31) & ~31) >> 5)
195 #define TINY_METADATA_SIZE (sizeof(region_trailer_t) + sizeof(tiny_header_inuse_pair_t) * CEIL_NUM_TINY_BLOCKS_WORDS)
196 #define TINY_REGION_SIZE \
197 ((NUM_TINY_BLOCKS * TINY_QUANTUM + TINY_METADATA_SIZE + vm_page_size - 1) & ~ (vm_page_size - 1))
198
199 #define TINY_METADATA_START (NUM_TINY_BLOCKS * TINY_QUANTUM)
200
201 /*
202 * Beginning and end pointers for a region's heap.
203 */
204 #define TINY_REGION_ADDRESS(region) ((void *)(region))
205 #define TINY_REGION_END(region) ((void *)(((uintptr_t)(region)) + (NUM_TINY_BLOCKS * TINY_QUANTUM)))
206
207 /*
208 * Locate the heap base for a pointer known to be within a tiny region.
209 */
210 #define TINY_REGION_FOR_PTR(_p) ((void *)((uintptr_t)(_p) & ~((1 << TINY_BLOCKS_ALIGN) - 1)))
211
212 /*
213 * Convert between byte and msize units.
214 */
215 #define TINY_BYTES_FOR_MSIZE(_m) ((_m) << SHIFT_TINY_QUANTUM)
216 #define TINY_MSIZE_FOR_BYTES(_b) ((_b) >> SHIFT_TINY_QUANTUM)
217
218 #ifdef __LP64__
219 # define TINY_FREE_SIZE(ptr) (((msize_t *)(ptr))[8])
220 #else
221 # define TINY_FREE_SIZE(ptr) (((msize_t *)(ptr))[4])
222 #endif
223 #define TINY_PREVIOUS_MSIZE(ptr) ((msize_t *)(ptr))[-1]
224
225 /*
226 * Layout of a tiny region
227 */
228 typedef uint32_t tiny_block_t[4]; // assert(TINY_QUANTUM == sizeof(tiny_block_t))
229
230 typedef struct tiny_header_inuse_pair
231 {
232 uint32_t header;
233 uint32_t inuse;
234 } tiny_header_inuse_pair_t;
235
236 typedef struct region_trailer
237 {
238 struct region_trailer *prev;
239 struct region_trailer *next;
240 boolean_t recirc_suitable;
241 unsigned bytes_used;
242 mag_index_t mag_index;
243 } region_trailer_t;
244
245 typedef struct tiny_region
246 {
247 tiny_block_t blocks[NUM_TINY_BLOCKS];
248
249 region_trailer_t trailer;
250
251 // The interleaved bit arrays comprising the header and inuse bitfields.
252 // The unused bits of each component in the last pair will be initialized to sentinel values.
253 tiny_header_inuse_pair_t pairs[CEIL_NUM_TINY_BLOCKS_WORDS];
254
255 uint8_t pad[TINY_REGION_SIZE - (NUM_TINY_BLOCKS * sizeof(tiny_block_t)) - TINY_METADATA_SIZE];
256 } *tiny_region_t;
257
258 /*
259 * Per-region meta data for tiny allocator
260 */
261 #define REGION_TRAILER_FOR_TINY_REGION(r) (&(((tiny_region_t)(r))->trailer))
262 #define MAGAZINE_INDEX_FOR_TINY_REGION(r) (REGION_TRAILER_FOR_TINY_REGION(r)->mag_index)
263 #define BYTES_USED_FOR_TINY_REGION(r) (REGION_TRAILER_FOR_TINY_REGION(r)->bytes_used)
264
265 /*
266 * Locate the block header for a pointer known to be within a tiny region.
267 */
268 #define TINY_BLOCK_HEADER_FOR_PTR(_p) ((void *)&(((tiny_region_t)TINY_REGION_FOR_PTR(_p))->pairs))
269
270 /*
271 * Locate the inuse map for a given block header pointer.
272 */
273 #define TINY_INUSE_FOR_HEADER(_h) ((void *)&(((tiny_header_inuse_pair_t *)(_h))->inuse))
274
275 /*
276 * Compute the bitmap index for a pointer known to be within a tiny region.
277 */
278 #define TINY_INDEX_FOR_PTR(_p) (((uintptr_t)(_p) >> SHIFT_TINY_QUANTUM) & (NUM_TINY_CEIL_BLOCKS - 1))
279
280 #define TINY_CACHE 1 // This governs a last-free cache of 1 that bypasses the free-list
281
282 #if ! TINY_CACHE
283 #warning TINY_CACHE turned off
284 #endif
285
286 #define TINY_REGION_PAYLOAD_BYTES (NUM_TINY_BLOCKS * TINY_QUANTUM)
287
288 /********************* DEFINITIONS for small ************************/
289
290 /*
291 * Memory in the Small range is allocated from regions (heaps) pointed to by the szone's hashed_regions
292 * pointer.
293 *
294 * Each region is laid out as a heap, followed by the metadata array, all within an 8MB (2^23) block.
295 * The array is arranged as an array of shorts, one for each SMALL_QUANTUM in the heap.
296 * This means there are 16320 512-blocks and the array is 16320*2 bytes, which totals 8388480, leaving
297 * 128 bytes unused.
298 *
299 * The MSB of each short is set for the first quantum in a free block. The low 15 bits encode the
300 * block size (in SMALL_QUANTUM units), or are zero if the quantum is not the first in a block.
301 *
302 * The szone maintains an array of 32 freelists, each of which is used to hold free objects
303 * of the corresponding quantum size.
304 *
305 * A free block is laid out as:
306 *
307 * Offset (32-bit mode) (64-bit mode)
308 * 0x0 0x0 : previous
309 * 0x4 0x08 : next
310 * 0x8 0x10 : size (in quantum counts)
311 * end - 2 end - 2 : size (in quantum counts)
312 * end end
313 *
314 * All fields are pointer-sized, except for the size which is an unsigned short.
315 *
316 */
317
318 #define SMALL_IS_FREE (1 << 15)
319
320 #define SHIFT_SMALL_QUANTUM (SHIFT_TINY_QUANTUM + 5) // 9
321 #define SMALL_QUANTUM (1 << SHIFT_SMALL_QUANTUM) // 512 bytes
322
323 #define FOLLOWING_SMALL_PTR(ptr,msize) (((unsigned char *)(ptr)) + ((msize) << SHIFT_SMALL_QUANTUM))
324
325 /*
326 * The number of slots in the free-list for small blocks. To avoid going to
327 * vm system as often on large memory machines, increase the number of free list
328 * spots above some amount of RAM installed in the system.
329 */
330 #define NUM_SMALL_SLOTS 32
331 #define NUM_SMALL_SLOTS_LARGEMEM 256
332 #define SMALL_BITMAP_WORDS 8
333
334 /*
335 * We can only represent up to 1<<15 for msize; but we choose to stay even below that to avoid the
336 * convention msize=0 => msize = (1<<15)
337 */
338 #define NUM_SMALL_BLOCKS 16320
339 #define SHIFT_SMALL_CEIL_BLOCKS 14 // ceil(log2(NUM_SMALL_BLOCKs))
340 #define NUM_SMALL_CEIL_BLOCKS (1 << SHIFT_SMALL_CEIL_BLOCKS)
341 #define SMALL_BLOCKS_ALIGN (SHIFT_SMALL_CEIL_BLOCKS + SHIFT_SMALL_QUANTUM) // 23
342
343 #define SMALL_METADATA_SIZE (sizeof(region_trailer_t) + NUM_SMALL_BLOCKS * sizeof(msize_t))
344 #define SMALL_REGION_SIZE \
345 ((NUM_SMALL_BLOCKS * SMALL_QUANTUM + SMALL_METADATA_SIZE + vm_page_size - 1) & ~ (vm_page_size - 1))
346
347 #define SMALL_METADATA_START (NUM_SMALL_BLOCKS * SMALL_QUANTUM)
348
349 /*
350 * Beginning and end pointers for a region's heap.
351 */
352 #define SMALL_REGION_ADDRESS(region) ((unsigned char *)region)
353 #define SMALL_REGION_END(region) (SMALL_REGION_ADDRESS(region) + (NUM_SMALL_BLOCKS * SMALL_QUANTUM))
354
355 /*
356 * Locate the heap base for a pointer known to be within a small region.
357 */
358 #define SMALL_REGION_FOR_PTR(_p) ((void *)((uintptr_t)(_p) & ~((1 << SMALL_BLOCKS_ALIGN) - 1)))
359
360 /*
361 * Convert between byte and msize units.
362 */
363 #define SMALL_BYTES_FOR_MSIZE(_m) ((_m) << SHIFT_SMALL_QUANTUM)
364 #define SMALL_MSIZE_FOR_BYTES(_b) ((_b) >> SHIFT_SMALL_QUANTUM)
365
366 #define SMALL_PREVIOUS_MSIZE(ptr) ((msize_t *)(ptr))[-1]
367
368 /*
369 * Layout of a small region
370 */
371 typedef uint32_t small_block_t[SMALL_QUANTUM/sizeof(uint32_t)];
372
373 typedef struct small_region
374 {
375 small_block_t blocks[NUM_SMALL_BLOCKS];
376
377 region_trailer_t trailer;
378
379 msize_t small_meta_words[NUM_SMALL_BLOCKS];
380
381 uint8_t pad[SMALL_REGION_SIZE - (NUM_SMALL_BLOCKS * sizeof(small_block_t)) - SMALL_METADATA_SIZE];
382 } *small_region_t;
383
384 /*
385 * Per-region meta data for small allocator
386 */
387 #define REGION_TRAILER_FOR_SMALL_REGION(r) (&(((small_region_t)(r))->trailer))
388 #define MAGAZINE_INDEX_FOR_SMALL_REGION(r) (REGION_TRAILER_FOR_SMALL_REGION(r)->mag_index)
389 #define BYTES_USED_FOR_SMALL_REGION(r) (REGION_TRAILER_FOR_SMALL_REGION(r)->bytes_used)
390
391 /*
392 * Locate the metadata base for a pointer known to be within a small region.
393 */
394 #define SMALL_META_HEADER_FOR_PTR(_p) (((small_region_t)SMALL_REGION_FOR_PTR(_p))->small_meta_words)
395
396 /*
397 * Compute the metadata index for a pointer known to be within a small region.
398 */
399 #define SMALL_META_INDEX_FOR_PTR(_p) (((uintptr_t)(_p) >> SHIFT_SMALL_QUANTUM) & (NUM_SMALL_CEIL_BLOCKS - 1))
400
401 /*
402 * Find the metadata word for a pointer known to be within a small region.
403 */
404 #define SMALL_METADATA_FOR_PTR(_p) (SMALL_META_HEADER_FOR_PTR(_p) + SMALL_META_INDEX_FOR_PTR(_p))
405
406 /*
407 * Determine whether a pointer known to be within a small region points to memory which is free.
408 */
409 #define SMALL_PTR_IS_FREE(_p) (*SMALL_METADATA_FOR_PTR(_p) & SMALL_IS_FREE)
410
411 /*
412 * Extract the msize value for a pointer known to be within a small region.
413 */
414 #define SMALL_PTR_SIZE(_p) (*SMALL_METADATA_FOR_PTR(_p) & ~SMALL_IS_FREE)
415
416 #define PROTECT_SMALL 0 // Should be 0: 1 is too slow for normal use
417
418 #define SMALL_CACHE 1
419 #if !SMALL_CACHE
420 #warning SMALL_CACHE turned off
421 #endif
422
423 #define SMALL_REGION_PAYLOAD_BYTES (NUM_SMALL_BLOCKS * SMALL_QUANTUM)
424
425 /************************* DEFINITIONS for large ****************************/
426
427 #define LARGE_THRESHOLD (15 * 1024) // strictly above this use "large"
428 #define LARGE_THRESHOLD_LARGEMEM (127 * 1024)
429
430 #if (LARGE_THRESHOLD > NUM_SMALL_SLOTS * SMALL_QUANTUM)
431 #error LARGE_THRESHOLD should always be less than NUM_SMALL_SLOTS * SMALL_QUANTUM
432 #endif
433
434 #if (LARGE_THRESHOLD_LARGEMEM > NUM_SMALL_SLOTS_LARGEMEM * SMALL_QUANTUM)
435 #error LARGE_THRESHOLD_LARGEMEM should always be less than NUM_SMALL_SLOTS * SMALL_QUANTUM
436 #endif
437
438 /*
439 * When all memory is touched after a copy, vm_copy() is always a lose
440 * But if the memory is only read, vm_copy() wins over memmove() at 3 or 4 pages
441 * (on a G3/300MHz)
442 *
443 * This must be larger than LARGE_THRESHOLD
444 */
445 #define VM_COPY_THRESHOLD (40 * 1024)
446 #define VM_COPY_THRESHOLD_LARGEMEM (128 * 1024)
447
448 typedef struct {
449 vm_address_t address;
450 vm_size_t size;
451 boolean_t did_madvise_reusable;
452 } large_entry_t;
453
454 #define LARGE_CACHE 1
455 #if !LARGE_CACHE
456 #warning LARGE_CACHE turned off
457 #endif
458 #if defined(__LP64__)
459 #define LARGE_ENTRY_CACHE_SIZE 16
460 #define LARGE_CACHE_SIZE_LIMIT ((vm_size_t)0x80000000) /* 2Gb */
461 #else
462 #define LARGE_ENTRY_CACHE_SIZE 8
463 #define LARGE_CACHE_SIZE_LIMIT ((vm_size_t)0x02000000) /* 32Mb */
464 #endif
465 #define LARGE_CACHE_SIZE_ENTRY_LIMIT (LARGE_CACHE_SIZE_LIMIT/LARGE_ENTRY_CACHE_SIZE)
466
467 /*******************************************************************************
468 * Definitions for region hash
469 ******************************************************************************/
470
471 typedef void * region_t;
472 typedef region_t * rgnhdl_t; /* A pointer into hashed_regions array. */
473
474 #define INITIAL_NUM_REGIONS_SHIFT 6 // log2(INITIAL_NUM_REGIONS)
475 #define INITIAL_NUM_REGIONS (1 << INITIAL_NUM_REGIONS_SHIFT) // Must be a power of 2!
476 #define HASHRING_OPEN_ENTRY ((region_t) 0) // Initial value and sentinel marking end of collision chain
477 #define HASHRING_REGION_DEALLOCATED ((region_t)-1) // Region at this slot reclaimed by OS
478 #define HASH_BLOCKS_ALIGN TINY_BLOCKS_ALIGN // MIN( TINY_BLOCKS_ALIGN, SMALL_BLOCKS_ALIGN, ... )
479
480 typedef struct region_hash_generation {
481 size_t num_regions_allocated;
482 size_t num_regions_allocated_shift; // log2(num_regions_allocated)
483 region_t *hashed_regions; // hashed by location
484 struct region_hash_generation *nextgen;
485 } region_hash_generation_t;
486
487 /*******************************************************************************
488 * Per-processor magazine for tiny and small allocators
489 ******************************************************************************/
490
491 typedef struct { // vm_allocate()'d, so the array of magazines is page-aligned to begin with.
492 // Take magazine_lock first, Depot lock when needed for recirc, then szone->{tiny,small}_regions_lock when needed for alloc
493 pthread_lock_t magazine_lock CACHE_ALIGN;
494
495 // One element deep "death row", optimizes malloc/free/malloc for identical size.
496 void *mag_last_free; // low SHIFT_{TINY,SMALL}_QUANTUM bits indicate the msize
497 region_t mag_last_free_rgn; // holds the region for mag_last_free
498
499 free_list_t *mag_free_list[256]; // assert( 256 >= MAX( NUM_TINY_SLOTS, NUM_SMALL_SLOTS_LARGEMEM ))
500 unsigned mag_bitmap[8]; // assert( sizeof(mag_bitmap) << 3 >= sizeof(mag_free_list)/sizeof(free_list_t) )
501
502 // the last free region in the last block is treated as a big block in use that is not accounted for
503 size_t mag_bytes_free_at_end;
504 region_t mag_last_region; // Valid iff mag_bytes_free_at_end > 0
505
506 // bean counting ...
507 unsigned mag_num_objects;
508 size_t mag_num_bytes_in_objects;
509 size_t num_bytes_in_magazine;
510
511 // recirculation list -- invariant: all regions owned by this magazine that meet the emptiness criteria
512 // are located nearer to the head of the list than any region that doesn't satisfy that criteria.
513 // Doubly linked list for efficient extraction.
514 unsigned recirculation_entries;
515 region_trailer_t *firstNode;
516 region_trailer_t *lastNode;
517
518 #if __LP64__
519 uint64_t pad[49]; // So sizeof(magazine_t) is 2560 bytes. FIXME: assert this at compile time
520 #else
521 uint32_t pad[45]; // So sizeof(magazine_t) is 1280 bytes. FIXME: assert this at compile time
522 #endif
523 } magazine_t;
524
525 #define TINY_MAX_MAGAZINES 16 /* MUST BE A POWER OF 2! */
526 #define TINY_MAGAZINE_PAGED_SIZE \
527 (((sizeof(magazine_t) * (TINY_MAX_MAGAZINES + 1)) + vm_page_size - 1) &\
528 ~ (vm_page_size - 1)) /* + 1 for the Depot */
529
530 #define SMALL_MAX_MAGAZINES 16 /* MUST BE A POWER OF 2! */
531 #define SMALL_MAGAZINE_PAGED_SIZE \
532 (((sizeof(magazine_t) * (SMALL_MAX_MAGAZINES + 1)) + vm_page_size - 1) &\
533 ~ (vm_page_size - 1)) /* + 1 for the Depot */
534
535 #define DEPOT_MAGAZINE_INDEX -1
536
537 /****************************** zone itself ***********************************/
538
539 /*
540 * Note that objects whose adddress are held in pointers here must be pursued
541 * individually in the {tiny,small}_in_use_enumeration() routines. See for
542 * example the treatment of region_hash_generation and tiny_magazines below.
543 */
544
545 typedef struct szone_s { // vm_allocate()'d, so page-aligned to begin with.
546 malloc_zone_t basic_zone;
547 pthread_key_t cpu_id_key;
548 unsigned debug_flags;
549 void *log_address;
550
551 /* Regions for tiny objects */
552 pthread_lock_t tiny_regions_lock CACHE_ALIGN;
553 size_t num_tiny_regions;
554 size_t num_tiny_regions_dealloc;
555 region_hash_generation_t *tiny_region_generation;
556 region_hash_generation_t trg[2];
557
558 int num_tiny_magazines;
559 unsigned num_tiny_magazines_mask;
560 int num_tiny_magazines_mask_shift;
561 magazine_t *tiny_magazines; // array of per-processor magazines
562
563 /* Regions for small objects */
564 pthread_lock_t small_regions_lock CACHE_ALIGN;
565 size_t num_small_regions;
566 size_t num_small_regions_dealloc;
567 region_hash_generation_t *small_region_generation;
568 region_hash_generation_t srg[2];
569
570 unsigned num_small_slots; // determined by physmem size
571
572 int num_small_magazines;
573 unsigned num_small_magazines_mask;
574 int num_small_magazines_mask_shift;
575 magazine_t *small_magazines; // array of per-processor magazines
576
577 /* large objects: all the rest */
578 pthread_lock_t large_szone_lock CACHE_ALIGN; // One customer at a time for large
579 unsigned num_large_objects_in_use;
580 unsigned num_large_entries;
581 large_entry_t *large_entries; // hashed by location; null entries don't count
582 size_t num_bytes_in_large_objects;
583
584 #if LARGE_CACHE
585 int large_entry_cache_oldest;
586 int large_entry_cache_newest;
587 large_entry_t large_entry_cache[LARGE_ENTRY_CACHE_SIZE]; // "death row" for large malloc/free
588 boolean_t large_legacy_reset_mprotect;
589 size_t large_entry_cache_hoard_bytes;
590 size_t large_entry_cache_hoard_lmit;
591 #endif
592
593 /* flag and limits pertaining to altered malloc behavior for systems with
594 large amounts of physical memory */
595 unsigned is_largemem;
596 unsigned large_threshold;
597 unsigned vm_copy_threshold;
598
599 /* security cookie */
600 uintptr_t cookie;
601
602 /* Initial region list */
603 region_t initial_tiny_regions[INITIAL_NUM_REGIONS];
604 region_t initial_small_regions[INITIAL_NUM_REGIONS];
605
606 /* The purgeable zone constructed by create_purgeable_zone() would like to hand off tiny and small
607 * allocations to the default scalable zone. Record the latter as the "helper" zone here. */
608 struct szone_s *helper_zone;
609 } szone_t;
610
611 #define SZONE_PAGED_SIZE ((sizeof(szone_t) + vm_page_size - 1) & ~ (vm_page_size - 1))
612
613 #if DEBUG_MALLOC || DEBUG_CLIENT
614 static void szone_sleep(void);
615 #endif
616 __private_extern__ void malloc_error_break(void);
617
618 // msg prints after fmt, ...
619 static NOINLINE void szone_error(szone_t *szone, int is_corruption, const char *msg, const void *ptr, const char *fmt, ...)
620 __printflike(5, 6);
621
622 static void protect(void *address, size_t size, unsigned protection, unsigned debug_flags);
623 static void *allocate_pages(szone_t *szone, size_t size, unsigned char align, unsigned debug_flags,
624 int vm_page_label);
625 static void deallocate_pages(szone_t *szone, void *addr, size_t size, unsigned debug_flags);
626 static int madvise_free_range(szone_t *szone, region_t r, uintptr_t pgLo, uintptr_t pgHi);
627 static kern_return_t _szone_default_reader(task_t task, vm_address_t address, vm_size_t size, void **ptr);
628
629 static INLINE mag_index_t mag_get_thread_index(szone_t *szone) ALWAYSINLINE;
630 static magazine_t *mag_lock_zine_for_region_trailer(szone_t *szone, magazine_t *magazines, region_trailer_t *trailer,
631 mag_index_t mag_index);
632
633 static INLINE rgnhdl_t hash_lookup_region_no_lock(region_t *regions, size_t num_entries, size_t shift, region_t r)
634 ALWAYSINLINE;
635 static void hash_region_insert_no_lock(region_t *regions, size_t num_entries, size_t shift, region_t r);
636 static region_t *hash_regions_alloc_no_lock(szone_t *szone, size_t num_entries);
637 static region_t *hash_regions_grow_no_lock(szone_t *szone, region_t *regions, size_t old_size,
638 size_t *mutable_shift, size_t *new_size);
639
640 static INLINE uintptr_t free_list_gen_checksum(uintptr_t ptr) ALWAYSINLINE;
641 static INLINE uintptr_t free_list_checksum_ptr(szone_t *szone, void *p) ALWAYSINLINE;
642 static INLINE void *free_list_unchecksum_ptr(szone_t *szone, ptr_union *ptr) ALWAYSINLINE;
643 static unsigned free_list_count(szone_t *szone, free_list_t *ptr);
644
645 static INLINE void recirc_list_extract(szone_t *szone, magazine_t *mag_ptr, region_trailer_t *node) ALWAYSINLINE;
646 static INLINE void recirc_list_splice_last(szone_t *szone, magazine_t *mag_ptr, region_trailer_t *node) ALWAYSINLINE;
647 static INLINE void recirc_list_splice_first(szone_t *szone, magazine_t *mag_ptr, region_trailer_t *node) ALWAYSINLINE;
648
649 static INLINE void BITARRAY_SET(uint32_t *bits, msize_t index) ALWAYSINLINE;
650 static INLINE void BITARRAY_CLR(uint32_t *bits, msize_t index) ALWAYSINLINE;
651 static INLINE boolean_t BITARRAY_BIT(uint32_t *bits, msize_t index) ALWAYSINLINE;
652
653 static msize_t get_tiny_free_size(const void *ptr);
654 static msize_t get_tiny_previous_free_msize(const void *ptr);
655 static INLINE msize_t get_tiny_meta_header(const void *ptr, boolean_t *is_free) ALWAYSINLINE;
656 static INLINE void set_tiny_meta_header_in_use(const void *ptr, msize_t msize) ALWAYSINLINE;
657 static INLINE void set_tiny_meta_header_in_use_1(const void *ptr) ALWAYSINLINE;
658 static INLINE void set_tiny_meta_header_middle(const void *ptr) ALWAYSINLINE;
659 static INLINE void set_tiny_meta_header_free(const void *ptr, msize_t msize) ALWAYSINLINE;
660 static INLINE boolean_t tiny_meta_header_is_free(const void *ptr) ALWAYSINLINE;
661 static INLINE void *tiny_previous_preceding_free(void *ptr, msize_t *prev_msize) ALWAYSINLINE;
662
663 static void tiny_free_list_add_ptr(szone_t *szone, magazine_t *tiny_mag_ptr, void *ptr, msize_t msize);
664 static void tiny_free_list_remove_ptr(szone_t *szone, magazine_t *tiny_mag_ptr, void *ptr, msize_t msize);
665 static INLINE region_t tiny_region_for_ptr_no_lock(szone_t *szone, const void *ptr) ALWAYSINLINE;
666
667 static void tiny_finalize_region(szone_t *szone, magazine_t *tiny_mag_ptr);
668 static int tiny_free_detach_region(szone_t *szone, magazine_t *tiny_mag_ptr, region_t r);
669 static size_t tiny_free_reattach_region(szone_t *szone, magazine_t *tiny_mag_ptr, region_t r);
670 static void tiny_free_scan_madvise_free(szone_t *szone, magazine_t *depot_ptr, region_t r);
671 static void tiny_free_try_depot_unmap_no_lock(szone_t *szone, magazine_t *depot_ptr, region_trailer_t *node);
672 static void tiny_free_do_recirc_to_depot(szone_t *szone, magazine_t *tiny_mag_ptr, mag_index_t mag_index);
673 static boolean_t tiny_get_region_from_depot(szone_t *szone, magazine_t *tiny_mag_ptr, mag_index_t mag_index);
674
675 static INLINE void tiny_free_no_lock(szone_t *szone, magazine_t *tiny_mag_ptr, mag_index_t mag_index, region_t region,
676 void *ptr, msize_t msize) ALWAYSINLINE;
677 static void *tiny_malloc_from_region_no_lock(szone_t *szone, magazine_t *tiny_mag_ptr, mag_index_t mag_index,
678 msize_t msize);
679 static boolean_t tiny_try_realloc_in_place(szone_t *szone, void *ptr, size_t old_size, size_t new_size);
680 static boolean_t tiny_check_region(szone_t *szone, region_t region);
681 static kern_return_t tiny_in_use_enumerator(task_t task, void *context, unsigned type_mask, szone_t *szone,
682 memory_reader_t reader, vm_range_recorder_t recorder);
683 static void *tiny_malloc_from_free_list(szone_t *szone, magazine_t *tiny_mag_ptr, mag_index_t mag_index,
684 msize_t msize);
685 static INLINE void *tiny_malloc_should_clear(szone_t *szone, msize_t msize, boolean_t cleared_requested) ALWAYSINLINE;
686 static INLINE void free_tiny(szone_t *szone, void *ptr, region_t tiny_region, size_t known_size) ALWAYSINLINE;
687 static void print_tiny_free_list(szone_t *szone);
688 static void print_tiny_region(boolean_t verbose, region_t region, size_t bytes_at_end);
689 static boolean_t tiny_free_list_check(szone_t *szone, grain_t slot);
690
691 static INLINE void small_meta_header_set_is_free(msize_t *meta_headers, unsigned index, msize_t msize) ALWAYSINLINE;
692 static INLINE void small_meta_header_set_in_use(msize_t *meta_headers, msize_t index, msize_t msize) ALWAYSINLINE;
693 static INLINE void small_meta_header_set_middle(msize_t *meta_headers, msize_t index) ALWAYSINLINE;
694 static void small_free_list_add_ptr(szone_t *szone, magazine_t *small_mag_ptr, void *ptr, msize_t msize);
695 static void small_free_list_remove_ptr(szone_t *szone, magazine_t *small_mag_ptr, void *ptr, msize_t msize);
696 static INLINE region_t small_region_for_ptr_no_lock(szone_t *szone, const void *ptr) ALWAYSINLINE;
697
698 static void small_finalize_region(szone_t *szone, magazine_t *small_mag_ptr);
699 static int small_free_detach_region(szone_t *szone, magazine_t *small_mag_ptr, region_t r);
700 static size_t small_free_reattach_region(szone_t *szone, magazine_t *small_mag_ptr, region_t r);
701 static void small_free_scan_depot_madvise_free(szone_t *szone, magazine_t *depot_ptr, region_t r);
702 static void small_free_try_depot_unmap_no_lock(szone_t *szone, magazine_t *depot_ptr, region_trailer_t *node);
703 static void small_free_do_recirc_to_depot(szone_t *szone, magazine_t *small_mag_ptr, mag_index_t mag_index);
704 static boolean_t small_get_region_from_depot(szone_t *szone, magazine_t *small_mag_ptr, mag_index_t mag_index);
705 static INLINE void small_free_no_lock(szone_t *szone, magazine_t *small_mag_ptr, mag_index_t mag_index, region_t region,
706 void *ptr, msize_t msize) ALWAYSINLINE;
707 static void *small_malloc_from_region_no_lock(szone_t *szone, magazine_t *small_mag_ptr, mag_index_t mag_index,
708 msize_t msize);
709 static boolean_t small_try_realloc_in_place(szone_t *szone, void *ptr, size_t old_size, size_t new_size);
710 static boolean_t small_check_region(szone_t *szone, region_t region);
711 static kern_return_t small_in_use_enumerator(task_t task, void *context, unsigned type_mask, szone_t *szone,
712 memory_reader_t reader, vm_range_recorder_t recorder);
713 static void *small_malloc_from_free_list(szone_t *szone, magazine_t *small_mag_ptr, mag_index_t mag_index,
714 msize_t msize);
715 static INLINE void *small_malloc_should_clear(szone_t *szone, msize_t msize, boolean_t cleared_requested) ALWAYSINLINE;
716 static INLINE void free_small(szone_t *szone, void *ptr, region_t small_region, size_t known_size) ALWAYSINLINE;
717 static void print_small_free_list(szone_t *szone);
718 static void print_small_region(szone_t *szone, boolean_t verbose, region_t region, size_t bytes_at_end);
719 static boolean_t small_free_list_check(szone_t *szone, grain_t grain);
720
721 #if DEBUG_MALLOC
722 static void large_debug_print(szone_t *szone);
723 #endif
724 static large_entry_t *large_entry_for_pointer_no_lock(szone_t *szone, const void *ptr);
725 static void large_entry_insert_no_lock(szone_t *szone, large_entry_t range);
726 static INLINE void large_entries_rehash_after_entry_no_lock(szone_t *szone, large_entry_t *entry) ALWAYSINLINE;
727 static INLINE large_entry_t *large_entries_alloc_no_lock(szone_t *szone, unsigned num) ALWAYSINLINE;
728 static void large_entries_free_no_lock(szone_t *szone, large_entry_t *entries, unsigned num,
729 vm_range_t *range_to_deallocate);
730 static large_entry_t *large_entries_grow_no_lock(szone_t *szone, vm_range_t *range_to_deallocate);
731 static vm_range_t large_entry_free_no_lock(szone_t *szone, large_entry_t *entry);
732 static NOINLINE kern_return_t large_in_use_enumerator(task_t task, void *context,
733 unsigned type_mask, vm_address_t large_entries_address,
734 unsigned num_entries, memory_reader_t reader, vm_range_recorder_t recorder);
735 static void *large_malloc(szone_t *szone, size_t num_pages, unsigned char alignment, boolean_t cleared_requested);
736 static NOINLINE void free_large(szone_t *szone, void *ptr);
737 static INLINE int large_try_realloc_in_place(szone_t *szone, void *ptr, size_t old_size, size_t new_size) ALWAYSINLINE;
738
739 /*
740 * Mark these NOINLINE to avoid bloating the purgeable zone call backs
741 */
742 static NOINLINE void szone_free(szone_t *szone, void *ptr);
743 static NOINLINE void *szone_malloc_should_clear(szone_t *szone, size_t size, boolean_t cleared_requested);
744 static NOINLINE void *szone_malloc(szone_t *szone, size_t size);
745 static NOINLINE void *szone_calloc(szone_t *szone, size_t num_items, size_t size);
746 static NOINLINE void *szone_valloc(szone_t *szone, size_t size);
747 static NOINLINE size_t szone_size_try_large(szone_t *szone, const void *ptr);
748 static NOINLINE size_t szone_size(szone_t *szone, const void *ptr);
749 static NOINLINE void *szone_realloc(szone_t *szone, void *ptr, size_t new_size);
750 static NOINLINE void *szone_memalign(szone_t *szone, size_t alignment, size_t size);
751 static NOINLINE void szone_free_definite_size(szone_t *szone, void *ptr, size_t size);
752 static NOINLINE unsigned szone_batch_malloc(szone_t *szone, size_t size, void **results, unsigned count);
753 static NOINLINE void szone_batch_free(szone_t *szone, void **to_be_freed, unsigned count);
754 static void szone_destroy(szone_t *szone);
755 static NOINLINE size_t szone_good_size(szone_t *szone, size_t size);
756
757 static NOINLINE boolean_t szone_check_all(szone_t *szone, const char *function);
758 static boolean_t szone_check(szone_t *szone);
759 static kern_return_t szone_ptr_in_use_enumerator(task_t task, void *context,
760 unsigned type_mask, vm_address_t zone_address,
761 memory_reader_t reader, vm_range_recorder_t recorder);
762 static NOINLINE void szone_print(szone_t *szone, boolean_t verbose);
763 static void szone_log(malloc_zone_t *zone, void *log_address);
764 static void szone_force_lock(szone_t *szone);
765 static void szone_force_unlock(szone_t *szone);
766 static boolean_t szone_locked(szone_t *szone);
767
768 static void szone_statistics(szone_t *szone, malloc_statistics_t *stats);
769
770 static void purgeable_free(szone_t *szone, void *ptr);
771 static void *purgeable_malloc(szone_t *szone, size_t size);
772 static void *purgeable_calloc(szone_t *szone, size_t num_items, size_t size);
773 static void *purgeable_valloc(szone_t *szone, size_t size);
774 static size_t purgeable_size(szone_t *szone, const void *ptr);
775 static void *purgeable_realloc(szone_t *szone, void *ptr, size_t new_size);
776 static void *purgeable_memalign(szone_t *szone, size_t alignment, size_t size);
777 static void purgeable_free_definite_size(szone_t *szone, void *ptr, size_t size);
778 static unsigned purgeable_batch_malloc(szone_t *szone, size_t size, void **results, unsigned count);
779 static void purgeable_batch_free(szone_t *szone, void **to_be_freed, unsigned count);
780 static void purgeable_destroy(szone_t *szone);
781 static size_t purgeable_good_size(szone_t *szone, size_t size);
782
783 static boolean_t purgeable_check(szone_t *szone);
784 static kern_return_t purgeable_ptr_in_use_enumerator(task_t task, void *context,
785 unsigned type_mask, vm_address_t zone_address,
786 memory_reader_t reader, vm_range_recorder_t recorder);
787 static void purgeable_print(szone_t *szone, boolean_t verbose);
788 static void purgeable_log(malloc_zone_t *zone, void *log_address);
789 static void purgeable_force_lock(szone_t *szone);
790 static void purgeable_force_unlock(szone_t *szone);
791 static boolean_t purgeable_locked(szone_t *szone);
792
793 static void purgeable_statistics(szone_t *szone, malloc_statistics_t *stats);
794
795 static void *frozen_malloc(szone_t *zone, size_t new_size);
796 static void *frozen_calloc(szone_t *zone, size_t num_items, size_t size);
797 static void *frozen_valloc(szone_t *zone, size_t new_size);
798 static void *frozen_realloc(szone_t *zone, void *ptr, size_t new_size);
799 static void frozen_free(szone_t *zone, void *ptr);
800 static void frozen_destroy(szone_t *zone);
801
802 #define SZONE_LOCK(szone) \
803 do { \
804 LOCK(szone->large_szone_lock); \
805 } while (0)
806
807 #define SZONE_UNLOCK(szone) \
808 do { \
809 UNLOCK(szone->large_szone_lock); \
810 } while (0)
811
812 #define SZONE_TRY_LOCK(szone) \
813 TRY_LOCK(szone->large_szone_lock);
814
815 #define SZONE_MAGAZINE_PTR_LOCK(szone, mag_ptr) \
816 do { \
817 LOCK(mag_ptr->magazine_lock); \
818 } while(0)
819
820 #define SZONE_MAGAZINE_PTR_UNLOCK(szone, mag_ptr) \
821 do { \
822 UNLOCK(mag_ptr->magazine_lock); \
823 } while(0)
824
825 #define SZONE_MAGAZINE_PTR_TRY_LOCK(szone, mag_ptr) \
826 TRY_LOCK(mag_ptr->magazine_lock);
827
828 #if DEBUG_MALLOC
829 # define LOG(szone,ptr) \
830 (szone->log_address && (((uintptr_t)szone->log_address == -1) || \
831 (szone->log_address == (void *)(ptr))))
832 #else
833 # define LOG(szone,ptr) 0
834 #endif
835
836 #if DEBUG_MALLOC || DEBUG_CLIENT
837 # define CHECK(szone,fun) \
838 if ((szone)->debug_flags & CHECK_REGIONS) \
839 szone_check_all(szone, fun)
840 #else
841 # define CHECK(szone,fun) \
842 do {} while (0)
843 #endif
844
845 /********************* VERY LOW LEVEL UTILITIES ************************/
846
847 #if DEBUG_MALLOC || DEBUG_CLIENT
848 static void
849 szone_sleep(void)
850 {
851
852 if (getenv("MallocErrorSleep")) {
853 _malloc_printf(ASL_LEVEL_NOTICE, "*** sleeping to help debug\n");
854 sleep(3600); // to help debug
855 }
856 }
857 #endif
858
859 extern const char *__crashreporter_info__;
860
861 // msg prints after fmt, ...
862 static NOINLINE void
863 szone_error(szone_t *szone, int is_corruption, const char *msg, const void *ptr, const char *fmt, ...)
864 {
865 va_list ap;
866 _SIMPLE_STRING b = _simple_salloc();
867
868 if (szone) SZONE_UNLOCK(szone); // FIXME: unlock magazine and region locks?
869 if (b) {
870 if (fmt) {
871 va_start(ap, fmt);
872 _simple_vsprintf(b, fmt, ap);
873 va_end(ap);
874 }
875 if (ptr) {
876 _simple_sprintf(b, "*** error for object %p: %s\n", ptr, msg);
877 } else {
878 _simple_sprintf(b, "*** error: %s\n", msg);
879 }
880 malloc_printf("%s*** set a breakpoint in malloc_error_break to debug\n", _simple_string(b));
881 } else {
882 /*
883 * Should only get here if vm_allocate() can't get a single page of
884 * memory, implying _simple_asl_log() would also fail. So we just
885 * print to the file descriptor.
886 */
887 if (fmt) {
888 va_start(ap, fmt);
889 _malloc_vprintf(MALLOC_PRINTF_NOLOG, fmt, ap);
890 va_end(ap);
891 }
892 if (ptr) {
893 _malloc_printf(MALLOC_PRINTF_NOLOG, "*** error for object %p: %s\n", ptr, msg);
894 } else {
895 _malloc_printf(MALLOC_PRINTF_NOLOG, "*** error: %s\n", msg);
896 }
897 _malloc_printf(MALLOC_PRINTF_NOLOG, "*** set a breakpoint in malloc_error_break to debug\n");
898 }
899 malloc_error_break();
900 #if DEBUG_MALLOC
901 szone_print(szone, 1);
902 szone_sleep();
903 #endif
904 #if DEBUG_CLIENT
905 szone_sleep();
906 #endif
907 // Call abort() if this is a memory corruption error and the abort on
908 // corruption flag is set, or if any error should abort.
909 if ((is_corruption && (szone->debug_flags & SCALABLE_MALLOC_ABORT_ON_CORRUPTION)) ||
910 (szone->debug_flags & SCALABLE_MALLOC_ABORT_ON_ERROR)) {
911 __crashreporter_info__ = b ? _simple_string(b) : msg;
912 abort();
913 } else if (b) {
914 _simple_sfree(b);
915 }
916 }
917
918 static void
919 protect(void *address, size_t size, unsigned protection, unsigned debug_flags)
920 {
921 kern_return_t err;
922
923 if (!(debug_flags & SCALABLE_MALLOC_DONT_PROTECT_PRELUDE)) {
924 err = vm_protect(mach_task_self(), (vm_address_t)(uintptr_t)address - vm_page_size, vm_page_size, 0, protection);
925 if (err) {
926 malloc_printf("*** can't protect(%p) region for prelude guard page at %p\n",
927 protection,(uintptr_t)address - (1 << vm_page_shift));
928 }
929 }
930 if (!(debug_flags & SCALABLE_MALLOC_DONT_PROTECT_POSTLUDE)) {
931 err = vm_protect(mach_task_self(), (vm_address_t)(uintptr_t)address + size, vm_page_size, 0, protection);
932 if (err) {
933 malloc_printf("*** can't protect(%p) region for postlude guard page at %p\n",
934 protection, (uintptr_t)address + size);
935 }
936 }
937 }
938
939 static void *
940 allocate_pages(szone_t *szone, size_t size, unsigned char align, unsigned debug_flags, int vm_page_label)
941 {
942 // align specifies a desired alignment (as a log) or 0 if no alignment requested
943 void *vm_addr;
944 uintptr_t addr = 0, aligned_address;
945 boolean_t add_guard_pages = debug_flags & SCALABLE_MALLOC_ADD_GUARD_PAGES;
946 boolean_t purgeable = debug_flags & SCALABLE_MALLOC_PURGEABLE;
947 size_t allocation_size = round_page(size);
948 size_t delta;
949 int flags = VM_MAKE_TAG(vm_page_label);
950
951 if (align) add_guard_pages = 0; // too cumbersome to deal with that
952 if (!allocation_size) allocation_size = 1 << vm_page_shift;
953 if (add_guard_pages) allocation_size += 2 * (1 << vm_page_shift);
954 if (align) allocation_size += (size_t)1 << align;
955 if (purgeable) flags |= VM_FLAGS_PURGABLE;
956
957 if (allocation_size < size) // size_t arithmetic wrapped!
958 return NULL;
959
960 vm_addr = mmap(0, allocation_size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, flags, 0);
961 if ((uintptr_t)vm_addr == -1) {
962 szone_error(szone, 0, "can't allocate region", NULL, "*** mmap(size=%lu) failed (error code=%d)\n",
963 allocation_size, errno);
964 return NULL;
965 }
966 addr = (uintptr_t)vm_addr;
967
968 if (align) {
969 aligned_address = (addr + ((uintptr_t)1 << align) - 1) & ~ (((uintptr_t)1 << align) - 1);
970 if (aligned_address != addr) {
971 delta = aligned_address - addr;
972 if (munmap((void *)addr, delta) == -1)
973 malloc_printf("*** munmap unaligned header failed with %d\n", errno);
974 addr = aligned_address;
975 allocation_size -= delta;
976 }
977 if (allocation_size > size) {
978 if (munmap((void *)(addr + size), allocation_size - size) == -1)
979 malloc_printf("*** munmap unaligned footer failed with %d\n", errno);
980 }
981 }
982 if (add_guard_pages) {
983 addr += (uintptr_t)1 << vm_page_shift;
984 protect((void *)addr, size, 0, debug_flags);
985 }
986 return (void *)addr;
987 }
988
989 static void
990 deallocate_pages(szone_t *szone, void *addr, size_t size, unsigned debug_flags)
991 {
992 int err;
993 boolean_t add_guard_pages = debug_flags & SCALABLE_MALLOC_ADD_GUARD_PAGES;
994
995 if (add_guard_pages) {
996 addr = (void *)((uintptr_t)addr - (1 << vm_page_shift));
997 size += 2 * (1 << vm_page_shift);
998 }
999 err = munmap(addr, size);
1000 if ((err == -1) && szone)
1001 szone_error(szone, 0, "Can't deallocate_pages region", addr, NULL);
1002 }
1003
1004 static int
1005 madvise_free_range(szone_t *szone, region_t r, uintptr_t pgLo, uintptr_t pgHi)
1006 {
1007 if (pgHi > pgLo) {
1008 size_t len = pgHi - pgLo;
1009
1010 #if DEBUG_MALLOC
1011 if (szone->debug_flags & SCALABLE_MALLOC_DO_SCRIBBLE)
1012 memset((void *)pgLo, 0xed, len); // Scribble on MADV_FREEd memory
1013 #endif
1014 MAGMALLOC_MADVFREEREGION((void *)szone, (void *)r, (void *)pgLo, len); // DTrace USDT Probe
1015 if (-1 == madvise((void *)pgLo, len, MADV_FREE_REUSABLE)) {
1016 /* -1 return: VM map entry change makes this unfit for reuse. Something evil lurks. */
1017 #if DEBUG_MALLOC
1018 szone_error(szone, 1, "madvise_free_range madvise(..., MADV_FREE_REUSABLE) failed", (void *)pgLo, NULL);
1019 #endif
1020 }
1021 }
1022 return 0;
1023 }
1024
1025 static kern_return_t
1026 _szone_default_reader(task_t task, vm_address_t address, vm_size_t size, void **ptr)
1027 {
1028 *ptr = (void *)address;
1029 return 0;
1030 }
1031
1032 // Multiplicative hash where the multiplier is a prime near (ULONG_MAX / phi). [phi = 1.618033...]
1033 // pthread_t's are page aligned, (sometimes even in ascending sequence). These hash well.
1034 // See Knuth TAOCP, Vol. 3.
1035 #if __LP64__
1036 #define HASH_SELF() \
1037 ((((uintptr_t)pthread_self()) >> vm_page_shift) * 11400714819323198549ULL) >> (64 - szone->num_tiny_magazines_mask_shift)
1038 #else
1039 #define HASH_SELF() \
1040 ((((uintptr_t)pthread_self()) >> vm_page_shift) * 2654435761UL) >> (32 - szone->num_tiny_magazines_mask_shift)
1041 #endif
1042
1043 #if defined(__i386__) || defined(__x86_64__)
1044 #define __APPLE_API_PRIVATE
1045 #include <machine/cpu_capabilities.h>
1046 #define _COMM_PAGE_VERSION_REQD 9
1047 #undef __APPLE_API_PRIVATE
1048
1049 /*
1050 * These commpage routines provide fast access to the logical cpu number
1051 * of the calling processor assuming no pre-emption occurs.
1052 */
1053 #define CPU_NUMBER() (((int (*)()) _COMM_PAGE_CPU_NUMBER)()) /* Zero-based */
1054
1055 static INLINE mag_index_t
1056 mag_get_thread_index(szone_t *szone)
1057 {
1058 if (!__is_threaded)
1059 return 0;
1060 else
1061 return CPU_NUMBER() & (TINY_MAX_MAGAZINES - 1);
1062 }
1063
1064 #elif defined(__arm__)
1065
1066 static INLINE mag_index_t
1067 mag_get_thread_index(szone_t *szone)
1068 {
1069 return 0;
1070 }
1071
1072 #else
1073 #warning deriving magazine index from pthread_self() [want processor number]
1074
1075 static INLINE mag_index_t
1076 mag_get_thread_index(szone_t *szone)
1077 {
1078 if (!__is_threaded)
1079 return 0;
1080 else if ((pthread_key_t) -1 == szone->cpu_id_key) { // In case pthread_key_create() failed.
1081 return HASH_SELF();
1082 } else {
1083 mag_index_t idx = (mag_index_t)(intptr_t)pthread_getspecific(szone->cpu_id_key);
1084
1085 // Has this thread been hinted with a non-zero value [i.e. 1 + cpuid()] ?
1086 // If so, bump down the hint to a zero-based magazine index and return it.
1087 if (idx) {
1088 return idx - 1;
1089 } else {
1090 // No hint available. Contruct a magazine index for this thread ...
1091 idx = HASH_SELF();
1092
1093 // bump up the hint to exclude zero and try to memorize it ...
1094 pthread_setspecific(szone->cpu_id_key, (const void *)((uintptr_t)idx + 1));
1095
1096 // and return the (zero-based) magazine index.
1097 return idx;
1098 }
1099 }
1100 }
1101 #endif
1102
1103 static magazine_t *
1104 mag_lock_zine_for_region_trailer(szone_t *szone, magazine_t *magazines, region_trailer_t *trailer, mag_index_t mag_index)
1105 {
1106 mag_index_t refreshed_index;
1107 magazine_t *mag_ptr = &(magazines[mag_index]);
1108
1109 // Take the lock on entry.
1110 SZONE_MAGAZINE_PTR_LOCK(szone, mag_ptr);
1111
1112 // Now in the time it took to acquire the lock, the region may have migrated
1113 // from one magazine to another. In which case the magazine lock we obtained
1114 // (namely magazines[mag_index].mag_lock) is stale. If so, keep on tryin' ...
1115 while (mag_index != (refreshed_index = trailer->mag_index)) { // Note assignment
1116
1117 SZONE_MAGAZINE_PTR_UNLOCK(szone, mag_ptr);
1118
1119 mag_index = refreshed_index;
1120 mag_ptr = &(magazines[mag_index]);
1121 SZONE_MAGAZINE_PTR_LOCK(szone, mag_ptr);
1122 }
1123
1124 return mag_ptr;
1125 }
1126
1127 /*******************************************************************************
1128 * Region hash implementation
1129 *
1130 * This is essentially a duplicate of the existing Large allocator hash, minus
1131 * the ability to remove entries. The two should be combined eventually.
1132 ******************************************************************************/
1133 #pragma mark region hash
1134
1135 /*
1136 * hash_lookup_region_no_lock - Scan a hash ring looking for an entry for a
1137 * given region.
1138 *
1139 * FIXME: If consecutive queries of the same region are likely, a one-entry
1140 * cache would likely be a significant performance win here.
1141 */
1142 static INLINE rgnhdl_t
1143 hash_lookup_region_no_lock(region_t *regions, size_t num_entries, size_t shift, region_t r) {
1144 size_t index, hash_index;
1145 rgnhdl_t entry;
1146
1147 if (!num_entries)
1148 return 0;
1149
1150 // Multiplicative hash where the multiplier is a prime near (ULONG_MAX / phi). [phi = 1.618033...]
1151 // Since the values of (((uintptr_t)r >> HASH_BLOCKS_ALIGN) are (roughly) an ascending sequence of integers,
1152 // this hash works really well. See Knuth TAOCP, Vol. 3.
1153 #if __LP64__
1154 index = hash_index = (((uintptr_t)r >> HASH_BLOCKS_ALIGN) * 11400714819323198549ULL) >> (64 - shift);
1155 #else
1156 index = hash_index = (((uintptr_t)r >> HASH_BLOCKS_ALIGN) * 2654435761UL) >> (32 - shift);
1157 #endif
1158 do {
1159 entry = regions + index;
1160 if (*entry == 0)
1161 return 0;
1162 if (*entry == r)
1163 return entry;
1164 if (++index == num_entries)
1165 index = 0;
1166 } while (index != hash_index);
1167 return 0;
1168 }
1169
1170 /*
1171 * hash_region_insert_no_lock - Insert a region into the hash ring.
1172 */
1173 static void
1174 hash_region_insert_no_lock(region_t *regions, size_t num_entries, size_t shift, region_t r) {
1175 size_t index, hash_index;
1176 rgnhdl_t entry;
1177
1178 // Multiplicative hash where the multiplier is a prime near (ULONG_MAX / phi). [phi = 1.618033...]
1179 // Since the values of (((uintptr_t)r >> HASH_BLOCKS_ALIGN) are (roughly) an ascending sequence of integers,
1180 // this hash works really well. See Knuth TAOCP, Vol. 3.
1181 #if __LP64__
1182 index = hash_index = (((uintptr_t)r >> HASH_BLOCKS_ALIGN) * 11400714819323198549ULL) >> (64 - shift);
1183 #else
1184 index = hash_index = (((uintptr_t)r >> HASH_BLOCKS_ALIGN) * 2654435761UL) >> (32 - shift);
1185 #endif
1186 do {
1187 entry = regions + index;
1188 if (*entry == HASHRING_OPEN_ENTRY || *entry == HASHRING_REGION_DEALLOCATED) {
1189 *entry = r;
1190 return;
1191 }
1192 if (++index == num_entries)
1193 index = 0;
1194 } while (index != hash_index);
1195 }
1196
1197 /*
1198 * hash_regions_alloc_no_lock - Allocate space for a number of entries. This
1199 * must be a VM allocation as to avoid recursing between allocating a new small
1200 * region, and asking the small region to allocate space for the new list of
1201 * regions.
1202 */
1203 static region_t *
1204 hash_regions_alloc_no_lock(szone_t *szone, size_t num_entries)
1205 {
1206 size_t size = num_entries * sizeof(region_t);
1207
1208 return allocate_pages(szone, round_page(size), 0, 0, VM_MEMORY_MALLOC);
1209 }
1210
1211 /*
1212 * hash_regions_grow_no_lock - Grow the hash ring, and rehash the entries.
1213 * Return the new region and new size to update the szone. Do not deallocate
1214 * the old entries since someone may still be allocating them.
1215 */
1216 static region_t *
1217 hash_regions_grow_no_lock(szone_t *szone, region_t *regions, size_t old_size, size_t *mutable_shift,
1218 size_t *new_size)
1219 {
1220 // double in size and allocate memory for the regions
1221 *new_size = old_size + old_size;
1222 *mutable_shift = *mutable_shift + 1;
1223 region_t *new_regions = hash_regions_alloc_no_lock(szone, *new_size);
1224
1225 // rehash the entries into the new list
1226 size_t index;
1227 for (index = 0; index < old_size; ++index) {
1228 region_t r = regions[index];
1229 if (r != HASHRING_OPEN_ENTRY && r != HASHRING_REGION_DEALLOCATED)
1230 hash_region_insert_no_lock(new_regions, *new_size, *mutable_shift, r);
1231 }
1232 return new_regions;
1233 }
1234
1235 /********************* FREE LIST UTILITIES ************************/
1236
1237 // A free list entry is comprised of a pair of pointers, previous and next.
1238 // These are used to implement a doubly-linked list, which permits efficient
1239 // extraction.
1240 //
1241 // Because the free list entries are previously freed objects, a misbehaved
1242 // program may write to a pointer after it has called free() on that pointer,
1243 // either by dereference or buffer overflow from an adjacent pointer. This write
1244 // would then corrupt the free list's previous and next pointers, leading to a
1245 // crash. In order to detect this case, we take advantage of the fact that
1246 // malloc'd pointers are known to be at least 16 byte aligned, and thus have
1247 // at least 4 trailing zero bits.
1248 //
1249 // When an entry is added to the free list, a checksum of the previous and next
1250 // pointers is calculated and written to the low four bits of the respective
1251 // pointers. Upon detection of an invalid checksum, an error is logged and NULL
1252 // is returned. Since all code which un-checksums pointers checks for a NULL
1253 // return, a potentially crashing or malicious dereference is avoided at the
1254 // cost of leaking the corrupted block, and any subsequent blocks on the free
1255 // list of that size.
1256
1257 static NOINLINE void
1258 free_list_checksum_botch(szone_t *szone, free_list_t *ptr)
1259 {
1260 szone_error(szone, 1, "incorrect checksum for freed object "
1261 "- object was probably modified after being freed.", ptr, NULL);
1262 }
1263
1264 static INLINE uintptr_t free_list_gen_checksum(uintptr_t ptr)
1265 {
1266 uint8_t chk;
1267
1268 chk = (unsigned char)(ptr >> 0);
1269 chk += (unsigned char)(ptr >> 8);
1270 chk += (unsigned char)(ptr >> 16);
1271 chk += (unsigned char)(ptr >> 24);
1272 #if __LP64__
1273 chk += (unsigned char)(ptr >> 32);
1274 chk += (unsigned char)(ptr >> 40);
1275 chk += (unsigned char)(ptr >> 48);
1276 chk += (unsigned char)(ptr >> 56);
1277 #endif
1278
1279 return chk & (uintptr_t)0xF;
1280 }
1281
1282 static INLINE uintptr_t
1283 free_list_checksum_ptr(szone_t *szone, void *ptr)
1284 {
1285 uintptr_t p = (uintptr_t)ptr;
1286 return p | free_list_gen_checksum(p ^ szone->cookie);
1287 }
1288
1289 static INLINE void *
1290 free_list_unchecksum_ptr(szone_t *szone, ptr_union *ptr)
1291 {
1292 ptr_union p;
1293 p.u = (ptr->u >> 4) << 4;
1294
1295 if ((ptr->u & (uintptr_t)0xF) != free_list_gen_checksum(p.u ^ szone->cookie))
1296 {
1297 free_list_checksum_botch(szone, (free_list_t *)ptr);
1298 return NULL;
1299 }
1300 return p.p;
1301 }
1302
1303 static unsigned
1304 free_list_count(szone_t *szone, free_list_t *ptr)
1305 {
1306 unsigned count = 0;
1307
1308 while (ptr) {
1309 count++;
1310 ptr = free_list_unchecksum_ptr(szone, &ptr->next);
1311 }
1312 return count;
1313 }
1314
1315 static INLINE void
1316 recirc_list_extract(szone_t *szone, magazine_t *mag_ptr, region_trailer_t *node)
1317 {
1318 // excise node from list
1319 if (NULL == node->prev)
1320 mag_ptr->firstNode = node->next;
1321 else
1322 node->prev->next = node->next;
1323
1324 if (NULL == node->next)
1325 mag_ptr->lastNode = node->prev;
1326 else
1327 node->next->prev = node->prev;
1328
1329 mag_ptr->recirculation_entries--;
1330 }
1331
1332 static INLINE void
1333 recirc_list_splice_last(szone_t *szone, magazine_t *mag_ptr, region_trailer_t *node)
1334 {
1335 if (NULL == mag_ptr->lastNode) {
1336 mag_ptr->firstNode = node;
1337 node->prev = NULL;
1338 } else {
1339 node->prev = mag_ptr->lastNode;
1340 mag_ptr->lastNode->next = node;
1341 }
1342 mag_ptr->lastNode = node;
1343 node->next = NULL;
1344 node->recirc_suitable = FALSE;
1345 mag_ptr->recirculation_entries++;
1346 }
1347
1348 static INLINE void
1349 recirc_list_splice_first(szone_t *szone, magazine_t *mag_ptr, region_trailer_t *node)
1350 {
1351 if (NULL == mag_ptr->firstNode) {
1352 mag_ptr->lastNode = node;
1353 node->next = NULL;
1354 } else {
1355 node->next = mag_ptr->firstNode;
1356 mag_ptr->firstNode->prev = node;
1357 }
1358 mag_ptr->firstNode = node;
1359 node->prev = NULL;
1360 node->recirc_suitable = FALSE;
1361 mag_ptr->recirculation_entries++;
1362 }
1363
1364 /* Macros used to manipulate the uint32_t quantity mag_bitmap. */
1365
1366 /* BITMAPV variants are used by tiny. */
1367 #if defined(__LP64__)
1368 // assert(NUM_SLOTS == 64) in which case (slot >> 5) is either 0 or 1
1369 #define BITMAPV_SET(bitmap,slot) (bitmap[(slot) >> 5] |= 1 << ((slot) & 31))
1370 #define BITMAPV_CLR(bitmap,slot) (bitmap[(slot) >> 5] &= ~ (1 << ((slot) & 31)))
1371 #define BITMAPV_BIT(bitmap,slot) ((bitmap[(slot) >> 5] >> ((slot) & 31)) & 1)
1372 #define BITMAPV_CTZ(bitmap) (__builtin_ctzl(bitmap))
1373 #else
1374 // assert(NUM_SLOTS == 32) in which case (slot >> 5) is always 0, so code it that way
1375 #define BITMAPV_SET(bitmap,slot) (bitmap[0] |= 1 << (slot))
1376 #define BITMAPV_CLR(bitmap,slot) (bitmap[0] &= ~ (1 << (slot)))
1377 #define BITMAPV_BIT(bitmap,slot) ((bitmap[0] >> (slot)) & 1)
1378 #define BITMAPV_CTZ(bitmap) (__builtin_ctz(bitmap))
1379 #endif
1380
1381 /* BITMAPN is used by small. (slot >> 5) takes on values from 0 to 7. */
1382 #define BITMAPN_SET(bitmap,slot) (bitmap[(slot) >> 5] |= 1 << ((slot) & 31))
1383 #define BITMAPN_CLR(bitmap,slot) (bitmap[(slot) >> 5] &= ~ (1 << ((slot) & 31)))
1384 #define BITMAPN_BIT(bitmap,slot) ((bitmap[(slot) >> 5] >> ((slot) & 31)) & 1)
1385
1386 /* returns bit # of least-significant one bit, starting at 0 (undefined if !bitmap) */
1387 #define BITMAP32_CTZ(bitmap) (__builtin_ctz(bitmap[0]))
1388
1389 /********************* TINY FREE LIST UTILITIES ************************/
1390
1391 // We encode the meta-headers as follows:
1392 // Each quantum has an associated set of 2 bits:
1393 // block_header when 1 says this block is the beginning of a block
1394 // in_use when 1 says this block is in use
1395 // so a block in use of size 3 is 1-1 0-X 0-X
1396 // for a free block TINY_FREE_SIZE(ptr) carries the size and the bits are 1-0 X-X X-X
1397 // for a block middle the bits are 0-0
1398
1399 // We store the meta-header bit arrays by interleaving them 32 bits at a time.
1400 // Initial 32 bits of block_header, followed by initial 32 bits of in_use, followed
1401 // by next 32 bits of block_header, followed by next 32 bits of in_use, etc.
1402 // This localizes memory references thereby reducing cache and TLB pressures.
1403
1404 static INLINE void
1405 BITARRAY_SET(uint32_t *bits, msize_t index)
1406 {
1407 // index >> 5 identifies the uint32_t to manipulate in the conceptually contiguous bits array
1408 // (index >> 5) << 1 identifies the uint32_t allowing for the actual interleaving
1409 bits[(index >> 5) << 1] |= (1 << (index & 31));
1410 }
1411
1412 static INLINE void
1413 BITARRAY_CLR(uint32_t *bits, msize_t index)
1414 {
1415 bits[(index >> 5) << 1] &= ~(1 << (index & 31));
1416 }
1417
1418 static INLINE boolean_t
1419 BITARRAY_BIT(uint32_t *bits, msize_t index)
1420 {
1421 return ((bits[(index >> 5) << 1]) >> (index & 31)) & 1;
1422 }
1423
1424 #if 0
1425 static INLINE void bitarray_mclr(uint32_t *bits, unsigned start, unsigned end) ALWAYSINLINE;
1426
1427 static INLINE void
1428 bitarray_mclr(uint32_t *bits, unsigned start, unsigned end)
1429 {
1430 // start >> 5 identifies the uint32_t to manipulate in the conceptually contiguous bits array
1431 // (start >> 5) << 1 identifies the uint32_t allowing for the actual interleaving
1432 uint32_t *addr = bits + ((start >> 5) << 1);
1433
1434 uint32_t span = end - start;
1435 start = start & 31;
1436 end = start + span;
1437
1438 if (end > 31) {
1439 addr[0] &= (0xFFFFFFFFU >> (31 - start)) >> 1;
1440 addr[2] &= (0xFFFFFFFFU << (end - 32));
1441 } else {
1442 unsigned mask = (0xFFFFFFFFU >> (31 - start)) >> 1;
1443 mask |= (0xFFFFFFFFU << end);
1444 addr[0] &= mask;
1445 }
1446 }
1447 #endif
1448
1449 /*
1450 * Obtain the size of a free tiny block (in msize_t units).
1451 */
1452 static msize_t
1453 get_tiny_free_size(const void *ptr)
1454 {
1455 void *next_block = (void *)((uintptr_t)ptr + TINY_QUANTUM);
1456 void *region_end = TINY_REGION_END(TINY_REGION_FOR_PTR(ptr));
1457
1458 // check whether the next block is outside the tiny region or a block header
1459 // if so, then the size of this block is one, and there is no stored size.
1460 if (next_block < region_end)
1461 {
1462 uint32_t *next_header = TINY_BLOCK_HEADER_FOR_PTR(next_block);
1463 msize_t next_index = TINY_INDEX_FOR_PTR(next_block);
1464
1465 if (!BITARRAY_BIT(next_header, next_index))
1466 return TINY_FREE_SIZE(ptr);
1467 }
1468 return 1;
1469 }
1470
1471 /*
1472 * Get the size of the previous free block, which is stored in the last two
1473 * bytes of the block. If the previous block is not free, then the result is
1474 * undefined.
1475 */
1476 static msize_t
1477 get_tiny_previous_free_msize(const void *ptr)
1478 {
1479 // check whether the previous block is in the tiny region and a block header
1480 // if so, then the size of the previous block is one, and there is no stored
1481 // size.
1482 if (ptr != TINY_REGION_FOR_PTR(ptr))
1483 {
1484 void *prev_block = (void *)((uintptr_t)ptr - TINY_QUANTUM);
1485 uint32_t *prev_header = TINY_BLOCK_HEADER_FOR_PTR(prev_block);
1486 msize_t prev_index = TINY_INDEX_FOR_PTR(prev_block);
1487 if (BITARRAY_BIT(prev_header, prev_index))
1488 return 1;
1489 return TINY_PREVIOUS_MSIZE(ptr);
1490 }
1491 // don't read possibly unmapped memory before the beginning of the region
1492 return 0;
1493 }
1494
1495 static INLINE msize_t
1496 get_tiny_meta_header(const void *ptr, boolean_t *is_free)
1497 {
1498 // returns msize and is_free
1499 // may return 0 for the msize component (meaning 65536)
1500 uint32_t *block_header;
1501 msize_t index;
1502
1503 block_header = TINY_BLOCK_HEADER_FOR_PTR(ptr);
1504 index = TINY_INDEX_FOR_PTR(ptr);
1505
1506 msize_t midx = (index >> 5) << 1;
1507 uint32_t mask = 1 << (index & 31);
1508 *is_free = 0;
1509 if (0 == (block_header[midx] & mask)) // if (!BITARRAY_BIT(block_header, index))
1510 return 0;
1511 if (0 == (block_header[midx + 1] & mask)) { // if (!BITARRAY_BIT(in_use, index))
1512 *is_free = 1;
1513 return get_tiny_free_size(ptr);
1514 }
1515
1516 // index >> 5 identifies the uint32_t to manipulate in the conceptually contiguous bits array
1517 // (index >> 5) << 1 identifies the uint32_t allowing for the actual interleaving
1518 #if defined(__LP64__)
1519 // The return value, msize, is computed as the distance to the next 1 bit in block_header.
1520 // That's guaranteed to be somewhwere in the next 64 bits. And those bits could span three
1521 // uint32_t block_header elements. Collect the bits into a single uint64_t and measure up with ffsl.
1522 uint32_t *addr = ((uint32_t *)block_header) + ((index >> 5) << 1);
1523 uint32_t bitidx = index & 31;
1524 uint64_t word_lo = addr[0];
1525 uint64_t word_mid = addr[2];
1526 uint64_t word_hi = addr[4];
1527 uint64_t word_lomid = (word_lo >> bitidx) | (word_mid << (32 - bitidx));
1528 uint64_t word = bitidx ? word_lomid | (word_hi << (64 - bitidx)) : word_lomid;
1529 uint32_t result = __builtin_ffsl(word >> 1);
1530 #else
1531 // The return value, msize, is computed as the distance to the next 1 bit in block_header.
1532 // That's guaranteed to be somwhwere in the next 32 bits. And those bits could span two
1533 // uint32_t block_header elements. Collect the bits into a single uint32_t and measure up with ffs.
1534 uint32_t *addr = ((uint32_t *)block_header) + ((index >> 5) << 1);
1535 uint32_t bitidx = index & 31;
1536 uint32_t word = bitidx ? (addr[0] >> bitidx) | (addr[2] << (32 - bitidx)) : addr[0];
1537 uint32_t result = __builtin_ffs(word >> 1);
1538 #endif
1539 return result;
1540 }
1541
1542 static INLINE void
1543 set_tiny_meta_header_in_use(const void *ptr, msize_t msize)
1544 {
1545 uint32_t *block_header = TINY_BLOCK_HEADER_FOR_PTR(ptr);
1546 msize_t index = TINY_INDEX_FOR_PTR(ptr);
1547 msize_t clr_msize = msize - 1;
1548 msize_t midx = (index >> 5) << 1;
1549 uint32_t val = (1 << (index & 31));
1550
1551 #if DEBUG_MALLOC
1552 if (msize >= NUM_TINY_SLOTS)
1553 malloc_printf("set_tiny_meta_header_in_use() invariant broken %p %d\n", ptr, msize);
1554 if ((unsigned)index + (unsigned)msize > 0x10000)
1555 malloc_printf("set_tiny_meta_header_in_use() invariant broken (2) %p %d\n", ptr, msize);
1556 #endif
1557
1558 block_header[midx] |= val; // BITARRAY_SET(block_header, index);
1559 block_header[midx + 1] |= val; // BITARRAY_SET(in_use, index);
1560
1561 // bitarray_mclr(block_header, index, end_bit);
1562 // bitarray_mclr(in_use, index, end_bit);
1563
1564 index++;
1565 midx = (index >> 5) << 1;
1566
1567 unsigned start = index & 31;
1568 unsigned end = start + clr_msize;
1569
1570 #if defined(__LP64__)
1571 if (end > 63) {
1572 unsigned mask0 = (0xFFFFFFFFU >> (31 - start)) >> 1;
1573 unsigned mask1 = (0xFFFFFFFFU << (end - 64));
1574 block_header[midx + 0] &= mask0; // clear header
1575 block_header[midx + 1] &= mask0; // clear in_use
1576 block_header[midx + 2] = 0; // clear header
1577 block_header[midx + 3] = 0; // clear in_use
1578 block_header[midx + 4] &= mask1; // clear header
1579 block_header[midx + 5] &= mask1; // clear in_use
1580 } else
1581 #endif
1582 if (end > 31) {
1583 unsigned mask0 = (0xFFFFFFFFU >> (31 - start)) >> 1;
1584 unsigned mask1 = (0xFFFFFFFFU << (end - 32));
1585 block_header[midx + 0] &= mask0;
1586 block_header[midx + 1] &= mask0;
1587 block_header[midx + 2] &= mask1;
1588 block_header[midx + 3] &= mask1;
1589 } else {
1590 unsigned mask = (0xFFFFFFFFU >> (31 - start)) >> 1;
1591 mask |= (0xFFFFFFFFU << end);
1592 block_header[midx + 0] &= mask;
1593 block_header[midx + 1] &= mask;
1594 }
1595
1596 // we set the block_header bit for the following block to reaffirm next block is a block
1597 index += clr_msize;
1598 midx = (index >> 5) << 1;
1599 val = (1 << (index & 31));
1600 block_header[midx] |= val; // BITARRAY_SET(block_header, (index+clr_msize));
1601 #if DEBUG_MALLOC
1602 {
1603 boolean_t ff;
1604 msize_t mf;
1605
1606 mf = get_tiny_meta_header(ptr, &ff);
1607 if (msize != mf) {
1608 malloc_printf("setting header for tiny in_use %p : %d\n", ptr, msize);
1609 malloc_printf("reading header for tiny %p : %d %d\n", ptr, mf, ff);
1610 }
1611 }
1612 #endif
1613 }
1614
1615 static INLINE void
1616 set_tiny_meta_header_in_use_1(const void *ptr) // As above with msize == 1
1617 {
1618 uint32_t *block_header = TINY_BLOCK_HEADER_FOR_PTR(ptr);
1619 msize_t index = TINY_INDEX_FOR_PTR(ptr);
1620 msize_t midx = (index >> 5) << 1;
1621 uint32_t val = (1 << (index & 31));
1622
1623 block_header[midx] |= val; // BITARRAY_SET(block_header, index);
1624 block_header[midx + 1] |= val; // BITARRAY_SET(in_use, index);
1625
1626 index++;
1627 midx = (index >> 5) << 1;
1628 val = (1 << (index & 31));
1629
1630 block_header[midx] |= val; // BITARRAY_SET(block_header, (index+clr_msize))
1631 }
1632
1633 static INLINE void
1634 set_tiny_meta_header_middle(const void *ptr)
1635 {
1636 // indicates this block is in the middle of an in use block
1637 uint32_t *block_header;
1638 uint32_t *in_use;
1639 msize_t index;
1640
1641 block_header = TINY_BLOCK_HEADER_FOR_PTR(ptr);
1642 in_use = TINY_INUSE_FOR_HEADER(block_header);
1643 index = TINY_INDEX_FOR_PTR(ptr);
1644
1645 BITARRAY_CLR(block_header, index);
1646 BITARRAY_CLR(in_use, index);
1647 }
1648
1649 static INLINE void
1650 set_tiny_meta_header_free(const void *ptr, msize_t msize)
1651 {
1652 // !msize is acceptable and means 65536
1653 uint32_t *block_header = TINY_BLOCK_HEADER_FOR_PTR(ptr);
1654 msize_t index = TINY_INDEX_FOR_PTR(ptr);
1655 msize_t midx = (index >> 5) << 1;
1656 uint32_t val = (1 << (index & 31));
1657
1658 #if DEBUG_MALLOC
1659 if ((unsigned)index + (unsigned)msize > 0x10000) {
1660 malloc_printf("setting header for tiny free %p msize too large: %d\n", ptr, msize);
1661 }
1662 #endif
1663
1664 block_header[midx] |= val; // BITARRAY_SET(block_header, index);
1665 block_header[midx + 1] &= ~val; // BITARRAY_CLR(in_use, index);
1666
1667 // mark the end of this block if msize is > 1. For msize == 0, the whole
1668 // region is free, so there is no following block. For msize == 1, there is
1669 // no space to write the size on 64 bit systems. The size for 1 quantum
1670 // blocks is computed from the metadata bitmaps.
1671 if (msize > 1) {
1672 void *follower = FOLLOWING_TINY_PTR(ptr, msize);
1673 TINY_PREVIOUS_MSIZE(follower) = msize;
1674 TINY_FREE_SIZE(ptr) = msize;
1675 }
1676 if (msize == 0) {
1677 TINY_FREE_SIZE(ptr) = msize;
1678 }
1679 #if DEBUG_MALLOC
1680 boolean_t ff;
1681 msize_t mf = get_tiny_meta_header(ptr, &ff);
1682 if ((msize != mf) || !ff) {
1683 malloc_printf("setting header for tiny free %p : %u\n", ptr, msize);
1684 malloc_printf("reading header for tiny %p : %u %u\n", ptr, mf, ff);
1685 }
1686 #endif
1687 }
1688
1689 static INLINE boolean_t
1690 tiny_meta_header_is_free(const void *ptr)
1691 {
1692 uint32_t *block_header;
1693 uint32_t *in_use;
1694 msize_t index;
1695
1696 block_header = TINY_BLOCK_HEADER_FOR_PTR(ptr);
1697 in_use = TINY_INUSE_FOR_HEADER(block_header);
1698 index = TINY_INDEX_FOR_PTR(ptr);
1699 if (!BITARRAY_BIT(block_header, index))
1700 return 0;
1701 return !BITARRAY_BIT(in_use, index);
1702 }
1703
1704 static INLINE void *
1705 tiny_previous_preceding_free(void *ptr, msize_t *prev_msize)
1706 {
1707 // returns the previous block, assuming and verifying it's free
1708 uint32_t *block_header;
1709 uint32_t *in_use;
1710 msize_t index;
1711 msize_t previous_msize;
1712 msize_t previous_index;
1713 void *previous_ptr;
1714
1715 block_header = TINY_BLOCK_HEADER_FOR_PTR(ptr);
1716 in_use = TINY_INUSE_FOR_HEADER(block_header);
1717 index = TINY_INDEX_FOR_PTR(ptr);
1718
1719 if (!index)
1720 return NULL;
1721 if ((previous_msize = get_tiny_previous_free_msize(ptr)) > index)
1722 return NULL;
1723
1724 previous_index = index - previous_msize;
1725 previous_ptr = (void *)((uintptr_t)TINY_REGION_FOR_PTR(ptr) + TINY_BYTES_FOR_MSIZE(previous_index));
1726 if (!BITARRAY_BIT(block_header, previous_index))
1727 return NULL;
1728 if (BITARRAY_BIT(in_use, previous_index))
1729 return NULL;
1730 if (get_tiny_free_size(previous_ptr) != previous_msize)
1731 return NULL;
1732
1733 // conservative check did match true check
1734 *prev_msize = previous_msize;
1735 return previous_ptr;
1736 }
1737
1738 /*
1739 * Adds an item to the proper free list, and also marks the meta-header of the
1740 * block properly.
1741 * Assumes szone has been locked
1742 */
1743 static void
1744 tiny_free_list_add_ptr(szone_t *szone, magazine_t *tiny_mag_ptr, void *ptr, msize_t msize)
1745 {
1746 grain_t slot = (!msize || (msize >= NUM_TINY_SLOTS)) ? NUM_TINY_SLOTS - 1 : msize - 1;
1747 free_list_t *free_ptr = ptr;
1748 free_list_t *free_head = tiny_mag_ptr->mag_free_list[slot];
1749
1750 #if DEBUG_MALLOC
1751 if (LOG(szone,ptr)) {
1752 malloc_printf("in %s, ptr=%p, msize=%d\n", __FUNCTION__, ptr, msize);
1753 }
1754 if (((uintptr_t)ptr) & (TINY_QUANTUM - 1)) {
1755 szone_error(szone, 1, "tiny_free_list_add_ptr: Unaligned ptr", ptr, NULL);
1756 }
1757 #endif
1758 set_tiny_meta_header_free(ptr, msize);
1759 if (free_head) {
1760 #if DEBUG_MALLOC
1761 if (free_list_unchecksum_ptr(szone, &free_head->previous)) {
1762 szone_error(szone, 1, "tiny_free_list_add_ptr: Internal invariant broken (free_head->previous)", ptr,
1763 "ptr=%p slot=%d free_head=%p previous=%p\n", ptr, slot, (void *)free_head, free_head->previous.p);
1764 }
1765 if (! tiny_meta_header_is_free(free_head)) {
1766 szone_error(szone, 1, "tiny_free_list_add_ptr: Internal invariant broken (free_head is not a free pointer)", ptr,
1767 "ptr=%p slot=%d free_head=%p\n", ptr, slot, (void *)free_head);
1768 }
1769 #endif
1770 free_head->previous.u = free_list_checksum_ptr(szone, free_ptr);
1771 } else {
1772 BITMAPV_SET(tiny_mag_ptr->mag_bitmap, slot);
1773 }
1774 free_ptr->previous.u = free_list_checksum_ptr(szone, NULL);
1775 free_ptr->next.u = free_list_checksum_ptr(szone, free_head);
1776
1777 tiny_mag_ptr->mag_free_list[slot] = free_ptr;
1778 }
1779
1780 /*
1781 * Removes the item pointed to by ptr in the proper free list.
1782 * Assumes szone has been locked
1783 */
1784 static void
1785 tiny_free_list_remove_ptr(szone_t *szone, magazine_t *tiny_mag_ptr, void *ptr, msize_t msize)
1786 {
1787 grain_t slot = (!msize || (msize >= NUM_TINY_SLOTS)) ? NUM_TINY_SLOTS - 1 : msize - 1;
1788 free_list_t *free_ptr = ptr, *next, *previous;
1789
1790 next = free_list_unchecksum_ptr(szone, &free_ptr->next);
1791 previous = free_list_unchecksum_ptr(szone, &free_ptr->previous);
1792
1793 #if DEBUG_MALLOC
1794 if (LOG(szone,ptr)) {
1795 malloc_printf("In %s, ptr=%p, msize=%d\n", __FUNCTION__, ptr, msize);
1796 }
1797 #endif
1798 if (!previous) {
1799 // The block to remove is the head of the free list
1800 #if DEBUG_MALLOC
1801 if (tiny_mag_ptr->mag_free_list[slot] != ptr) {
1802 szone_error(szone, 1, "tiny_free_list_remove_ptr: Internal invariant broken (tiny_mag_ptr->mag_free_list[slot])", ptr,
1803 "ptr=%p slot=%d msize=%d tiny_mag_ptr->mag_free_list[slot]=%p\n",
1804 ptr, slot, msize, (void *)tiny_mag_ptr->mag_free_list[slot]);
1805 return;
1806 }
1807 #endif
1808 tiny_mag_ptr->mag_free_list[slot] = next;
1809 if (!next) BITMAPV_CLR(tiny_mag_ptr->mag_bitmap, slot);
1810 } else {
1811 // We know free_ptr is already checksummed, so we don't need to do it
1812 // again.
1813 previous->next = free_ptr->next;
1814 }
1815 if (next) {
1816 // We know free_ptr is already checksummed, so we don't need to do it
1817 // again.
1818 next->previous = free_ptr->previous;
1819 }
1820 }
1821
1822 /*
1823 * tiny_region_for_ptr_no_lock - Returns the tiny region containing the pointer,
1824 * or NULL if not found.
1825 */
1826 static INLINE region_t
1827 tiny_region_for_ptr_no_lock(szone_t *szone, const void *ptr)
1828 {
1829 rgnhdl_t r = hash_lookup_region_no_lock(szone->tiny_region_generation->hashed_regions,
1830 szone->tiny_region_generation->num_regions_allocated,
1831 szone->tiny_region_generation->num_regions_allocated_shift,
1832 TINY_REGION_FOR_PTR(ptr));
1833 return r ? *r : r;
1834 }
1835
1836 static void
1837 tiny_finalize_region(szone_t *szone, magazine_t *tiny_mag_ptr) {
1838 void *last_block, *previous_block;
1839 uint32_t *last_header;
1840 msize_t last_msize, previous_msize, last_index;
1841
1842 last_block = (void *)
1843 ((uintptr_t)TINY_REGION_END(tiny_mag_ptr->mag_last_region) - tiny_mag_ptr->mag_bytes_free_at_end);
1844 last_msize = TINY_MSIZE_FOR_BYTES(tiny_mag_ptr->mag_bytes_free_at_end);
1845 last_header = TINY_BLOCK_HEADER_FOR_PTR(last_block);
1846 last_index = TINY_INDEX_FOR_PTR(last_block);
1847
1848 // Before anything we transform any remaining mag_bytes_free_at_end into a
1849 // regular free block. We take special care here to update the bitfield
1850 // information, since we are bypassing the normal free codepath. If there
1851 // is more than one quanta worth of memory in mag_bytes_free_at_end, then
1852 // there will be two block headers:
1853 // 1) header for the free space at end, msize = 1
1854 // 2) header inserted by set_tiny_meta_header_in_use after block
1855 // We must clear the second one so that when the free block's size is
1856 // queried, we do not think the block is only 1 quantum in size because
1857 // of the second set header bit.
1858 if (last_index != (NUM_TINY_BLOCKS - 1))
1859 BITARRAY_CLR(last_header, (last_index + 1));
1860
1861 // It is possible that the block prior to the last block in the region has
1862 // been free'd, but was not coalesced with the free bytes at the end of the
1863 // block, since we treat the bytes at the end of the region as "in use" in
1864 // the meta headers. Attempt to coalesce the last block with the previous
1865 // block, so we don't violate the "no consecutive free blocks" invariant.
1866 //
1867 // FIXME: Need to investigate how much work would be required to increase
1868 // 'mag_bytes_free_at_end' when freeing the preceding block, rather
1869 // than performing this workaround.
1870 //
1871 previous_block = tiny_previous_preceding_free(last_block, &previous_msize);
1872 if (previous_block) {
1873 set_tiny_meta_header_middle(last_block);
1874 tiny_free_list_remove_ptr(szone, tiny_mag_ptr, previous_block, previous_msize);
1875 last_block = previous_block;
1876 last_msize += previous_msize;
1877 }
1878
1879 // splice last_block into the free list
1880 tiny_free_list_add_ptr(szone, tiny_mag_ptr, last_block, last_msize);
1881 tiny_mag_ptr->mag_bytes_free_at_end = 0;
1882 tiny_mag_ptr->mag_last_region = NULL;
1883 }
1884
1885 static int
1886 tiny_free_detach_region(szone_t *szone, magazine_t *tiny_mag_ptr, region_t r) {
1887 uintptr_t start = (uintptr_t)TINY_REGION_ADDRESS(r);
1888 uintptr_t current = start;
1889 uintptr_t limit = (uintptr_t)TINY_REGION_END(r);
1890 boolean_t is_free;
1891 msize_t msize;
1892 int total_alloc = 0;
1893
1894 while (current < limit) {
1895 msize = get_tiny_meta_header((void *)current, &is_free);
1896 if (is_free && !msize && (current == start)) {
1897 // first block is all free
1898 break;
1899 }
1900 if (!msize) {
1901 #if DEBUG_MALLOC
1902 malloc_printf("*** tiny_free_detach_region error with %p: msize=%d is_free =%d\n",
1903 (void *)current, msize, is_free);
1904 #endif
1905 break;
1906 }
1907 if (is_free) {
1908 tiny_free_list_remove_ptr(szone, tiny_mag_ptr, (void *)current, msize);
1909 } else {
1910 total_alloc++;
1911 }
1912 current += TINY_BYTES_FOR_MSIZE(msize);
1913 }
1914 return total_alloc;
1915 }
1916
1917 static size_t
1918 tiny_free_reattach_region(szone_t *szone, magazine_t *tiny_mag_ptr, region_t r) {
1919 uintptr_t start = (uintptr_t)TINY_REGION_ADDRESS(r);
1920 uintptr_t current = start;
1921 uintptr_t limit = (uintptr_t)TINY_REGION_END(r);
1922 boolean_t is_free;
1923 msize_t msize;
1924 size_t total_alloc = 0;
1925
1926 while (current < limit) {
1927 msize = get_tiny_meta_header((void *)current, &is_free);
1928 if (is_free && !msize && (current == start)) {
1929 // first block is all free
1930 break;
1931 }
1932 if (!msize) {
1933 #if DEBUG_MALLOC
1934 malloc_printf("*** tiny_free_reattach_region error with %p: msize=%d is_free =%d\n",
1935 (void *)current, msize, is_free);
1936 #endif
1937 break;
1938 }
1939 if (is_free) {
1940 tiny_free_list_add_ptr(szone, tiny_mag_ptr, (void *)current, msize);
1941 } else {
1942 total_alloc += TINY_BYTES_FOR_MSIZE(msize);
1943 }
1944 current += TINY_BYTES_FOR_MSIZE(msize);
1945 }
1946 return total_alloc;
1947 }
1948
1949 static void
1950 tiny_free_scan_madvise_free(szone_t *szone, magazine_t *depot_ptr, region_t r) {
1951 uintptr_t start = (uintptr_t)TINY_REGION_ADDRESS(r);
1952 uintptr_t current = start;
1953 uintptr_t limit = (uintptr_t)TINY_REGION_END(r);
1954 boolean_t is_free;
1955 msize_t msize;
1956 boolean_t did_advise = FALSE;
1957
1958 // Scan the metadata identifying blocks which span one or more pages. Mark the pages MADV_FREE taking care to preserve free list
1959 // management data.
1960 while (current < limit) {
1961 msize = get_tiny_meta_header((void *)current, &is_free);
1962 if (is_free && !msize && (current == start)) {
1963 // first block is all free
1964 #if DEBUG_MALLOC
1965 malloc_printf("*** tiny_free_scan_madvise_free first block is all free! %p: msize=%d is_free =%d\n",
1966 (void *)current, msize, is_free);
1967 #endif
1968 uintptr_t pgLo = round_page(start + sizeof(free_list_t) + sizeof(msize_t));
1969 uintptr_t pgHi = trunc_page(start + TINY_REGION_SIZE - sizeof(msize_t));
1970
1971 if (pgLo < pgHi) {
1972 madvise_free_range(szone, r, pgLo, pgHi);
1973 did_advise = TRUE;
1974 }
1975 break;
1976 }
1977 if (!msize) {
1978 #if DEBUG_MALLOC
1979 malloc_printf("*** tiny_free_scan_madvise_free error with %p: msize=%d is_free =%d\n",
1980 (void *)current, msize, is_free);
1981 #endif
1982 break;
1983 }
1984 if (is_free) {
1985 uintptr_t pgLo = round_page(current + sizeof(free_list_t) + sizeof(msize_t));
1986 uintptr_t pgHi = trunc_page(current + TINY_BYTES_FOR_MSIZE(msize) - sizeof(msize_t));
1987
1988 if (pgLo < pgHi) {
1989 madvise_free_range(szone, r, pgLo, pgHi);
1990 did_advise = TRUE;
1991 }
1992 }
1993 current += TINY_BYTES_FOR_MSIZE(msize);
1994 }
1995
1996 if (did_advise) {
1997 /* Move the node to the tail of the Deopt's recirculation list to delay its re-use. */
1998 region_trailer_t *node = REGION_TRAILER_FOR_TINY_REGION(r);
1999 recirc_list_extract(szone, depot_ptr, node); // excise node from list
2000 recirc_list_splice_last(szone, depot_ptr, node); // connect to magazine as last node
2001 }
2002 }
2003
2004 static void
2005 tiny_free_try_depot_unmap_no_lock(szone_t *szone, magazine_t *depot_ptr, region_trailer_t *node)
2006 {
2007 #warning Tune Depot headroom
2008 if (0 < node->bytes_used ||
2009 depot_ptr->recirculation_entries < (szone->num_tiny_magazines * 2)) {
2010 return;
2011 }
2012
2013 // disconnect node from Depot
2014 recirc_list_extract(szone, depot_ptr, node);
2015
2016 // Iterate the region pulling its free entries off the (locked) Depot's free list
2017 region_t sparse_region = TINY_REGION_FOR_PTR(node);
2018 int objects_in_use = tiny_free_detach_region(szone, depot_ptr, sparse_region);
2019
2020 if (0 == objects_in_use) {
2021 // Invalidate the hash table entry for this region with HASHRING_REGION_DEALLOCATED.
2022 // Using HASHRING_REGION_DEALLOCATED preserves the collision chain, using HASHRING_OPEN_ENTRY (0) would not.
2023 rgnhdl_t pSlot = hash_lookup_region_no_lock(szone->tiny_region_generation->hashed_regions,
2024 szone->tiny_region_generation->num_regions_allocated,
2025 szone->tiny_region_generation->num_regions_allocated_shift, sparse_region);
2026 *pSlot = HASHRING_REGION_DEALLOCATED;
2027 depot_ptr->num_bytes_in_magazine -= TINY_REGION_PAYLOAD_BYTES;
2028 #if ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ >= 1))) /* GCC 4.1 and forward supports atomic builtins */
2029 __sync_fetch_and_add( &(szone->num_tiny_regions_dealloc), 1); // Atomically increment num_tiny_regions_dealloc
2030 #else
2031 #ifdef __LP64__
2032 OSAtomicIncrement64( (volatile int64_t *)&(szone->num_tiny_regions_dealloc) );
2033 #else
2034 OSAtomicIncrement32( (volatile int32_t *)&(szone->num_tiny_regions_dealloc) );
2035 #endif
2036 #endif
2037
2038 // Transfer ownership of the region back to the OS
2039 SZONE_MAGAZINE_PTR_UNLOCK(szone, depot_ptr); // Avoid denial of service to Depot while in kernel
2040 deallocate_pages(szone, sparse_region, TINY_REGION_SIZE, 0);
2041 SZONE_MAGAZINE_PTR_LOCK(szone, depot_ptr);
2042
2043 MAGMALLOC_DEALLOCREGION((void *)szone, (void *)sparse_region); // DTrace USDT Probe
2044
2045 } else {
2046 szone_error(szone, 1, "tiny_free_try_depot_unmap_no_lock objects_in_use not zero:", NULL, "%d\n", objects_in_use);
2047 }
2048 }
2049
2050 static void
2051 tiny_free_do_recirc_to_depot(szone_t *szone, magazine_t *tiny_mag_ptr, mag_index_t mag_index)
2052 {
2053 // The entire magazine crossed the "emptiness threshold". Transfer a region
2054 // from this magazine to the Depot. Choose a region that itself has crossed the emptiness threshold (i.e
2055 // is at least fraction "f" empty.) Such a region will be marked "suitable" on the recirculation list.
2056 region_trailer_t *node = tiny_mag_ptr->firstNode;
2057
2058 while (node && !node->recirc_suitable) {
2059 node = node->next;
2060 }
2061
2062 if (NULL == node) {
2063 #if DEBUG_MALLOC
2064 malloc_printf("*** tiny_free_do_recirc_to_depot end of list\n");
2065 #endif
2066 return;
2067 }
2068
2069 region_t sparse_region = TINY_REGION_FOR_PTR(node);
2070
2071 // Deal with unclaimed memory -- mag_bytes_free_at_end
2072 if (sparse_region == tiny_mag_ptr->mag_last_region && tiny_mag_ptr->mag_bytes_free_at_end) {
2073 tiny_finalize_region(szone, tiny_mag_ptr);
2074 }
2075
2076 // disconnect "suitable" node from magazine
2077 recirc_list_extract(szone, tiny_mag_ptr, node);
2078
2079 // Iterate the region pulling its free entries off its (locked) magazine's free list
2080 int objects_in_use = tiny_free_detach_region(szone, tiny_mag_ptr, sparse_region);
2081 magazine_t *depot_ptr = &(szone->tiny_magazines[DEPOT_MAGAZINE_INDEX]);
2082
2083 // hand over the region to the (locked) Depot
2084 SZONE_MAGAZINE_PTR_LOCK(szone,depot_ptr);
2085 // this will cause tiny_free_list_add_ptr called by tiny_free_reattach_region to use
2086 // the depot as its target magazine, rather than magazine formerly associated with sparse_region
2087 MAGAZINE_INDEX_FOR_TINY_REGION(sparse_region) = DEPOT_MAGAZINE_INDEX;
2088
2089 // Iterate the region putting its free entries on Depot's free list
2090 size_t bytes_inplay = tiny_free_reattach_region(szone, depot_ptr, sparse_region);
2091
2092 tiny_mag_ptr->mag_num_bytes_in_objects -= bytes_inplay;
2093 tiny_mag_ptr->num_bytes_in_magazine -= TINY_REGION_PAYLOAD_BYTES;
2094 tiny_mag_ptr->mag_num_objects -= objects_in_use;
2095
2096 depot_ptr->mag_num_bytes_in_objects += bytes_inplay;
2097 depot_ptr->num_bytes_in_magazine += TINY_REGION_PAYLOAD_BYTES;
2098 depot_ptr->mag_num_objects += objects_in_use;
2099
2100 // connect to Depot as first (MRU) node
2101 recirc_list_splice_first(szone, depot_ptr, node);
2102
2103 MAGMALLOC_RECIRCREGION((void *)szone, (int)mag_index, (int)BYTES_USED_FOR_TINY_REGION(sparse_region)); // DTrace USDT Probe
2104
2105 // Mark free'd dirty pages with MADV_FREE to reduce memory pressure
2106 tiny_free_scan_madvise_free(szone, depot_ptr, sparse_region);
2107
2108 // If the region is entirely empty vm_deallocate() it
2109 tiny_free_try_depot_unmap_no_lock(szone, depot_ptr, node);
2110
2111 SZONE_MAGAZINE_PTR_UNLOCK(szone,depot_ptr);
2112 }
2113
2114 static boolean_t
2115 tiny_get_region_from_depot(szone_t *szone, magazine_t *tiny_mag_ptr, mag_index_t mag_index)
2116 {
2117 magazine_t *depot_ptr = &(szone->tiny_magazines[DEPOT_MAGAZINE_INDEX]);
2118
2119 /* FIXME: Would Uniprocessor benefit from recirc and MADV_FREE? */
2120 if (szone->num_tiny_magazines == 1) // Uniprocessor, single magazine, so no recirculation necessary
2121 return 0;
2122
2123 #if DEBUG_MALLOC
2124 if (DEPOT_MAGAZINE_INDEX == mag_index) {
2125 szone_error(szone, 1, "tiny_get_region_from_depot called for magazine index -1", NULL, NULL);
2126 return 0;
2127 }
2128 #endif
2129
2130 SZONE_MAGAZINE_PTR_LOCK(szone,depot_ptr);
2131
2132 // Appropriate one of the Depot's regions. Prefer LIFO selection for best cache utilization.
2133 region_trailer_t *node = depot_ptr->firstNode;
2134
2135 if (NULL == node) { // Depot empty?
2136 SZONE_MAGAZINE_PTR_UNLOCK(szone,depot_ptr);
2137 return 0;
2138 }
2139
2140 // disconnect first node from Depot
2141 recirc_list_extract(szone, depot_ptr, node);
2142
2143 // Iterate the region pulling its free entries off the (locked) Depot's free list
2144 region_t sparse_region = TINY_REGION_FOR_PTR(node);
2145 int objects_in_use = tiny_free_detach_region(szone, depot_ptr, sparse_region);
2146
2147 // Transfer ownership of the region
2148 MAGAZINE_INDEX_FOR_TINY_REGION(sparse_region) = mag_index;
2149
2150 // Iterate the region putting its free entries on its new (locked) magazine's free list
2151 size_t bytes_inplay = tiny_free_reattach_region(szone, tiny_mag_ptr, sparse_region);
2152
2153 depot_ptr->mag_num_bytes_in_objects -= bytes_inplay;
2154 depot_ptr->num_bytes_in_magazine -= TINY_REGION_PAYLOAD_BYTES;
2155 depot_ptr->mag_num_objects -= objects_in_use;
2156
2157 tiny_mag_ptr->mag_num_bytes_in_objects += bytes_inplay;
2158 tiny_mag_ptr->num_bytes_in_magazine += TINY_REGION_PAYLOAD_BYTES;
2159 tiny_mag_ptr->mag_num_objects += objects_in_use;
2160
2161 // connect to magazine as first node (it's maximally sparse at this moment)
2162 recirc_list_splice_first(szone, tiny_mag_ptr, node);
2163
2164 SZONE_MAGAZINE_PTR_UNLOCK(szone,depot_ptr);
2165
2166 MAGMALLOC_DEPOTREGION((void *)szone, (int)mag_index, (int)BYTES_USED_FOR_TINY_REGION(sparse_region)); // DTrace USDT Probe
2167
2168 if (-1 == madvise((void *)sparse_region, TINY_REGION_PAYLOAD_BYTES, MADV_FREE_REUSE)) {
2169 /* -1 return: VM map entry change makes this unfit for reuse. Something evil lurks. */
2170 #if DEBUG_MALLOC
2171 szone_error(szone, 1, "tiny_get_region_from_depot madvise(..., MADV_FREE_REUSE) failed", sparse_region, NULL);
2172 #endif
2173 return 0;
2174 }
2175
2176 return 1;
2177 }
2178
2179 #warning Tune K and f!
2180 #define K 1.5 // headroom measured in number of 1Mb regions
2181 #define DENSITY_THRESHOLD(a) \
2182 ((a) - ((a) >> 2)) // "Emptiness" f = 0.25, so "Density" is (1 - f)*a. Generally: ((a) - ((a) >> -log2(f)))
2183
2184 static INLINE void
2185 tiny_free_no_lock(szone_t *szone, magazine_t *tiny_mag_ptr, mag_index_t mag_index, region_t region, void *ptr,
2186 msize_t msize)
2187 {
2188 void *original_ptr = ptr;
2189 size_t original_size = TINY_BYTES_FOR_MSIZE(msize);
2190 void *next_block = ((unsigned char *)ptr + original_size);
2191 msize_t previous_msize, next_msize;
2192 void *previous;
2193 free_list_t *big_free_block;
2194 free_list_t *after_next_block;
2195 free_list_t *before_next_block;
2196 boolean_t did_prepend = FALSE;
2197 boolean_t did_append = FALSE;
2198
2199 #if DEBUG_MALLOC
2200 if (LOG(szone,ptr)) {
2201 malloc_printf("in tiny_free_no_lock(), ptr=%p, msize=%d\n", ptr, msize);
2202 }
2203 if (! msize) {
2204 szone_error(szone, 1, "trying to free tiny block that is too small", ptr,
2205 "in tiny_free_no_lock(), ptr=%p, msize=%d\n", ptr, msize);
2206 }
2207 #endif
2208
2209 // We try to coalesce this block with the preceeding one
2210 previous = tiny_previous_preceding_free(ptr, &previous_msize);
2211 if (previous) {
2212 #if DEBUG_MALLOC
2213 if (LOG(szone, ptr) || LOG(szone,previous)) {
2214 malloc_printf("in tiny_free_no_lock(), coalesced backwards for %p previous=%p\n", ptr, previous);
2215 }
2216 #endif
2217 did_prepend = TRUE;
2218
2219 // clear the meta_header since this is no longer the start of a block
2220 set_tiny_meta_header_middle(ptr);
2221 tiny_free_list_remove_ptr(szone, tiny_mag_ptr, previous, previous_msize);
2222 ptr = previous;
2223 msize += previous_msize;
2224 }
2225 // We try to coalesce with the next block
2226 if ((next_block < TINY_REGION_END(region)) && tiny_meta_header_is_free(next_block)) {
2227 did_append = TRUE;
2228 next_msize = get_tiny_free_size(next_block);
2229 #if DEBUG_MALLOC
2230 if (LOG(szone, ptr) || LOG(szone, next_block)) {
2231 malloc_printf("in tiny_free_no_lock(), for ptr=%p, msize=%d coalesced forward=%p next_msize=%d\n",
2232 ptr, msize, next_block, next_msize);
2233 }
2234 #endif
2235 // If we are coalescing with the next block, and the next block is in
2236 // the last slot of the free list, then we optimize this case here to
2237 // avoid removing next_block from the slot (NUM_TINY_SLOTS - 1) and then adding ptr back
2238 // to slot (NUM_TINY_SLOTS - 1).
2239 if (next_msize >= NUM_TINY_SLOTS) {
2240 msize += next_msize;
2241
2242 big_free_block = (free_list_t *)next_block;
2243 after_next_block = free_list_unchecksum_ptr(szone, &big_free_block->next);
2244 before_next_block = free_list_unchecksum_ptr(szone, &big_free_block->previous);
2245
2246 if (!before_next_block) {
2247 tiny_mag_ptr->mag_free_list[NUM_TINY_SLOTS-1] = ptr;
2248 } else {
2249 before_next_block->next.u = free_list_checksum_ptr(szone, ptr);
2250 }
2251
2252 if (after_next_block) {
2253 after_next_block->previous.u = free_list_checksum_ptr(szone, ptr);
2254 }
2255
2256 // we don't need to checksum these since they are already checksummed
2257 ((free_list_t *)ptr)->previous = big_free_block->previous;
2258 ((free_list_t *)ptr)->next = big_free_block->next;
2259
2260 // clear the meta_header to enable coalescing backwards
2261 set_tiny_meta_header_middle(big_free_block);
2262 set_tiny_meta_header_free(ptr, msize);
2263
2264 goto tiny_free_ending;
2265 }
2266 tiny_free_list_remove_ptr(szone, tiny_mag_ptr, next_block, next_msize);
2267 set_tiny_meta_header_middle(next_block); // clear the meta_header to enable coalescing backwards
2268 msize += next_msize;
2269 }
2270 #if !TINY_CACHE
2271 // The tiny cache already scribbles free blocks as they go through the
2272 // cache, so we do not need to do it here.
2273 if ((szone->debug_flags & SCALABLE_MALLOC_DO_SCRIBBLE) && msize)
2274 memset(ptr, 0x55, TINY_BYTES_FOR_MSIZE(msize));
2275
2276 #endif
2277 tiny_free_list_add_ptr(szone, tiny_mag_ptr, ptr, msize);
2278 tiny_free_ending:
2279 // When in proper debug mode we write on the memory to help debug memory smashers
2280
2281 tiny_mag_ptr->mag_num_objects--;
2282 // we use original_size and not msize to avoid double counting the coalesced blocks
2283 tiny_mag_ptr->mag_num_bytes_in_objects -= original_size;
2284
2285 // Update this region's bytes in use count
2286 region_trailer_t *node = REGION_TRAILER_FOR_TINY_REGION(region);
2287 size_t bytes_used = node->bytes_used - original_size;
2288 node->bytes_used = bytes_used;
2289
2290 /* FIXME: Would Uniprocessor benefit from recirc and MADV_FREE? */
2291 if (szone->num_tiny_magazines == 1) { // Uniprocessor, single magazine, so no recirculation necessary
2292 /* NOTHING */
2293 } else if (DEPOT_MAGAZINE_INDEX != mag_index) {
2294 // Emptiness discriminant
2295 if (bytes_used < DENSITY_THRESHOLD(TINY_REGION_PAYLOAD_BYTES)) {
2296 /* Region has crossed threshold from density to sparsity. Mark it "suitable" on the
2297 recirculation candidates list. */
2298 node->recirc_suitable = TRUE;
2299 } else {
2300 /* After this free, we've found the region is still dense, so it must have been even more so before
2301 the free. That implies the region is already correctly marked. Do nothing. */
2302 }
2303
2304 // Has the entire magazine crossed the "emptiness threshold"? If so, transfer a region
2305 // from this magazine to the Depot. Choose a region that itself has crossed the emptiness threshold (i.e
2306 // is at least fraction "f" empty.) Such a region will be marked "suitable" on the recirculation list.
2307 size_t a = tiny_mag_ptr->num_bytes_in_magazine; // Total bytes allocated to this magazine
2308 size_t u = tiny_mag_ptr->mag_num_bytes_in_objects; // In use (malloc'd) from this magaqzine
2309
2310 if (a - u > ((3 * TINY_REGION_PAYLOAD_BYTES) / 2) && u < DENSITY_THRESHOLD(a))
2311 tiny_free_do_recirc_to_depot(szone, tiny_mag_ptr, mag_index);
2312
2313 } else {
2314 // Freed to Depot. N.B. Lock on tiny_magazines[DEPOT_MAGAZINE_INDEX] is already held
2315 uintptr_t safe_ptr = (uintptr_t)ptr + sizeof(free_list_t) + sizeof(msize_t);
2316 uintptr_t round_safe = round_page(safe_ptr);
2317
2318 uintptr_t safe_extent = (uintptr_t)ptr + TINY_BYTES_FOR_MSIZE(msize) - sizeof(msize_t);
2319 uintptr_t trunc_extent = trunc_page(safe_extent);
2320
2321 // The newly freed block may complete a span of bytes that cover a page. Mark it with MADV_FREE.
2322 if (round_safe < trunc_extent) { // Safe area covers a page
2323 if (did_prepend & did_append) { // Coalesced preceding with original_ptr *and* with following
2324 uintptr_t trunc_safe_prev = trunc_page((uintptr_t)original_ptr - sizeof(msize_t));
2325 uintptr_t rnd_safe_follow =
2326 round_page((uintptr_t)original_ptr + original_size + sizeof(free_list_t) + sizeof(msize_t));
2327
2328 madvise_free_range(szone, region, MAX(round_safe, trunc_safe_prev), MIN(rnd_safe_follow, trunc_extent));
2329 } else if (did_prepend) { // Coalesced preceding with original_ptr
2330 uintptr_t trunc_safe_prev = trunc_page((uintptr_t)original_ptr - sizeof(msize_t));
2331
2332 madvise_free_range(szone, region, MAX(round_safe, trunc_safe_prev), trunc_extent);
2333 } else if (did_append) { // Coalesced original_ptr with following
2334 uintptr_t rnd_safe_follow =
2335 round_page((uintptr_t)original_ptr + original_size + sizeof(free_list_t) + sizeof(msize_t));
2336
2337 madvise_free_range(szone, region, round_safe, MIN(rnd_safe_follow, trunc_extent));
2338 } else { // Isolated free cannot exceed 496 bytes, thus round_safe == trunc_extent, and so never get here.
2339 /* madvise_free_range(szone, region, round_safe, trunc_extent); */
2340 }
2341 }
2342
2343 if (0 < bytes_used) {
2344 /* Depot'd region is still live. Leave it in place on the Depot's recirculation list
2345 so as to avoid thrashing between the Depot's free list and a magazines's free list
2346 with detach_region/reattach_region */
2347 } else {
2348 /* Depot'd region is just now empty. Consider return to OS. */
2349 region_trailer_t *node = REGION_TRAILER_FOR_TINY_REGION(region);
2350 magazine_t *depot_ptr = &(szone->tiny_magazines[DEPOT_MAGAZINE_INDEX]);
2351 tiny_free_try_depot_unmap_no_lock(szone, depot_ptr, node); // FIXME: depot_ptr is simply tiny_mag_ptr?
2352 }
2353 }
2354 }
2355
2356 // Allocates from the last region or a freshly allocated region
2357 static void *
2358 tiny_malloc_from_region_no_lock(szone_t *szone, magazine_t *tiny_mag_ptr, mag_index_t mag_index, msize_t msize)
2359 {
2360 void *ptr, *aligned_address;
2361
2362 // Deal with unclaimed memory -- mag_bytes_free_at_end
2363 if (tiny_mag_ptr->mag_bytes_free_at_end)
2364 tiny_finalize_region(szone, tiny_mag_ptr);
2365
2366 // time to create a new region
2367 aligned_address = allocate_pages(szone, TINY_REGION_SIZE, TINY_BLOCKS_ALIGN, 0, VM_MEMORY_MALLOC_TINY);
2368 if (!aligned_address) // out of memory!
2369 return NULL;
2370
2371 MAGMALLOC_ALLOCREGION((void *)szone, (int)mag_index); // DTrace USDT Probe
2372
2373 // We set the unused bits of the header in the last pair to be all ones, and those of the inuse to zeroes.
2374 ((tiny_region_t)aligned_address)->pairs[CEIL_NUM_TINY_BLOCKS_WORDS-1].header =
2375 (NUM_TINY_BLOCKS & 31) ? (0xFFFFFFFFU << (NUM_TINY_BLOCKS & 31)) : 0;
2376 ((tiny_region_t)aligned_address)->pairs[CEIL_NUM_TINY_BLOCKS_WORDS-1].inuse = 0;
2377
2378 // Here find the only place in tinyland that (infrequently) takes the tiny_regions_lock.
2379 // Only one thread at a time should be permitted to assess the density of the hash
2380 // ring and adjust if needed.
2381 // Only one thread at a time should be permitted to insert its new region on
2382 // the hash ring.
2383 // It is safe for all other threads to read the hash ring (hashed_regions) and
2384 // the associated sizes (num_regions_allocated and num_tiny_regions).
2385
2386 LOCK(szone->tiny_regions_lock);
2387
2388 // Check to see if the hash ring of tiny regions needs to grow. Try to
2389 // avoid the hash ring becoming too dense.
2390 if (szone->tiny_region_generation->num_regions_allocated < (2 * szone->num_tiny_regions)) {
2391 region_t *new_regions;
2392 size_t new_size;
2393 size_t new_shift = szone->tiny_region_generation->num_regions_allocated_shift; // In/Out parameter
2394 new_regions = hash_regions_grow_no_lock(szone, szone->tiny_region_generation->hashed_regions,
2395 szone->tiny_region_generation->num_regions_allocated,
2396 &new_shift,
2397 &new_size);
2398 // Do not deallocate the current hashed_regions allocation since someone may
2399 // be iterating it. Instead, just leak it.
2400
2401 // Prepare to advance to the "next generation" of the hash ring.
2402 szone->tiny_region_generation->nextgen->hashed_regions = new_regions;
2403 szone->tiny_region_generation->nextgen->num_regions_allocated = new_size;
2404 szone->tiny_region_generation->nextgen->num_regions_allocated_shift = new_shift;
2405
2406 // Throw the switch to atomically advance to the next generation.
2407 szone->tiny_region_generation = szone->tiny_region_generation->nextgen;
2408 // Ensure everyone sees the advance.
2409 #if ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ >= 1))) /* GCC 4.1 and forward supports atomic builtins */
2410 __sync_synchronize();
2411 #else
2412 OSMemoryBarrier();
2413 #endif
2414 }
2415 // Tag the region at "aligned_address" as belonging to us,
2416 // and so put it under the protection of the magazine lock we are holding.
2417 // Do this before advertising "aligned_address" on the hash ring(!)
2418 MAGAZINE_INDEX_FOR_TINY_REGION(aligned_address) = mag_index;
2419
2420 // Insert the new region into the hash ring, and update malloc statistics
2421 hash_region_insert_no_lock(szone->tiny_region_generation->hashed_regions,
2422 szone->tiny_region_generation->num_regions_allocated,
2423 szone->tiny_region_generation->num_regions_allocated_shift,
2424 aligned_address);
2425
2426 szone->num_tiny_regions++;
2427 UNLOCK(szone->tiny_regions_lock);
2428
2429 tiny_mag_ptr->mag_last_region = aligned_address;
2430 BYTES_USED_FOR_TINY_REGION(aligned_address) = TINY_BYTES_FOR_MSIZE(msize);
2431 ptr = aligned_address;
2432 set_tiny_meta_header_in_use(ptr, msize);
2433 tiny_mag_ptr->mag_num_objects++;
2434 tiny_mag_ptr->mag_num_bytes_in_objects += TINY_BYTES_FOR_MSIZE(msize);
2435 tiny_mag_ptr->num_bytes_in_magazine += TINY_REGION_PAYLOAD_BYTES;
2436
2437 // We put a header on the last block so that it appears in use (for coalescing, etc...)
2438 set_tiny_meta_header_in_use_1((void *)((uintptr_t)ptr + TINY_BYTES_FOR_MSIZE(msize)));
2439 tiny_mag_ptr->mag_bytes_free_at_end = TINY_BYTES_FOR_MSIZE(NUM_TINY_BLOCKS - msize);
2440
2441 // connect to magazine as first node (it's maximally sparse at this moment)
2442 recirc_list_splice_first(szone, tiny_mag_ptr, REGION_TRAILER_FOR_TINY_REGION(aligned_address));
2443
2444 #if DEBUG_MALLOC
2445 if (LOG(szone,ptr)) {
2446 malloc_printf("in tiny_malloc_from_region_no_lock(), ptr=%p, msize=%d\n", ptr, msize);
2447 }
2448 #endif
2449 return ptr;
2450 }
2451
2452 static INLINE boolean_t
2453 tiny_try_realloc_in_place(szone_t *szone, void *ptr, size_t old_size, size_t new_size)
2454 {
2455 // returns 1 on success
2456 msize_t index;
2457 msize_t old_msize;
2458 unsigned next_index;
2459 void *next_block;
2460 boolean_t is_free;
2461 msize_t next_msize, coalesced_msize, leftover_msize;
2462 void *leftover;
2463
2464 index = TINY_INDEX_FOR_PTR(ptr);
2465 old_msize = TINY_MSIZE_FOR_BYTES(old_size);
2466 next_index = index + old_msize;
2467
2468 if (next_index >= NUM_TINY_BLOCKS) {
2469 return 0;
2470 }
2471 next_block = (char *)ptr + old_size;
2472
2473 magazine_t *tiny_mag_ptr = mag_lock_zine_for_region_trailer(szone, szone->tiny_magazines,
2474 REGION_TRAILER_FOR_TINY_REGION(TINY_REGION_FOR_PTR(ptr)),
2475 MAGAZINE_INDEX_FOR_TINY_REGION(TINY_REGION_FOR_PTR(ptr)));
2476
2477 /*
2478 * Look for a free block immediately afterwards. If it's large enough, we can consume (part of)
2479 * it.
2480 */
2481 is_free = tiny_meta_header_is_free(next_block);
2482 if (!is_free) {
2483 SZONE_MAGAZINE_PTR_UNLOCK(szone,tiny_mag_ptr);
2484 return 0; // next_block is in use;
2485 }
2486 next_msize = get_tiny_free_size(next_block);
2487 if (old_size + TINY_BYTES_FOR_MSIZE(next_msize) < new_size) {
2488 SZONE_MAGAZINE_PTR_UNLOCK(szone,tiny_mag_ptr);
2489 return 0; // even with next block, not enough
2490 }
2491 /*
2492 * The following block is big enough; pull it from its freelist and chop off enough to satisfy
2493 * our needs.
2494 */
2495 tiny_free_list_remove_ptr(szone, tiny_mag_ptr, next_block, next_msize);
2496 set_tiny_meta_header_middle(next_block); // clear the meta_header to enable coalescing backwards
2497 coalesced_msize = TINY_MSIZE_FOR_BYTES(new_size - old_size + TINY_QUANTUM - 1);
2498 leftover_msize = next_msize - coalesced_msize;
2499 if (leftover_msize) {
2500 /* there's some left, so put the remainder back */
2501 leftover = (void *)((uintptr_t)next_block + TINY_BYTES_FOR_MSIZE(coalesced_msize));
2502
2503 tiny_free_list_add_ptr(szone, tiny_mag_ptr, leftover, leftover_msize);
2504 }
2505 set_tiny_meta_header_in_use(ptr, old_msize + coalesced_msize);
2506 #if DEBUG_MALLOC
2507 if (LOG(szone,ptr)) {
2508 malloc_printf("in tiny_try_realloc_in_place(), ptr=%p, msize=%d\n", ptr, old_msize + coalesced_msize);
2509 }
2510 #endif
2511 tiny_mag_ptr->mag_num_bytes_in_objects += TINY_BYTES_FOR_MSIZE(coalesced_msize);
2512
2513 // Update this region's bytes in use count
2514 region_trailer_t *node = REGION_TRAILER_FOR_TINY_REGION(TINY_REGION_FOR_PTR(ptr));
2515 size_t bytes_used = node->bytes_used + TINY_BYTES_FOR_MSIZE(coalesced_msize);
2516 node->bytes_used = bytes_used;
2517
2518 // Emptiness discriminant
2519 if (bytes_used < DENSITY_THRESHOLD(TINY_REGION_PAYLOAD_BYTES)) {
2520 /* After this reallocation the region is still sparse, so it must have been even more so before
2521 the reallocation. That implies the region is already correctly marked. Do nothing. */
2522 } else {
2523 /* Region has crossed threshold from sparsity to density. Mark it not "suitable" on the
2524 recirculation candidates list. */
2525 node->recirc_suitable = FALSE;
2526 }
2527
2528 SZONE_MAGAZINE_PTR_UNLOCK(szone,tiny_mag_ptr);
2529 CHECK(szone, __PRETTY_FUNCTION__);
2530 return 1;
2531 }
2532
2533 static boolean_t
2534 tiny_check_region(szone_t *szone, region_t region)
2535 {
2536 uintptr_t start, ptr, region_end;
2537 boolean_t prev_free = 0;
2538 boolean_t is_free;
2539 msize_t msize;
2540 free_list_t *free_head;
2541 void *follower, *previous, *next;
2542 mag_index_t mag_index = MAGAZINE_INDEX_FOR_TINY_REGION(region);
2543 magazine_t *tiny_mag_ptr = &(szone->tiny_magazines[mag_index]);
2544
2545 // Assumes locked
2546 CHECK_MAGAZINE_PTR_LOCKED(szone, tiny_mag_ptr, __PRETTY_FUNCTION__);
2547
2548 /* establish region limits */
2549 start = (uintptr_t)TINY_REGION_ADDRESS(region);
2550 ptr = start;
2551 region_end = (uintptr_t)TINY_REGION_END(region);
2552
2553 /*
2554 * The last region may have a trailing chunk which has not been converted into inuse/freelist
2555 * blocks yet.
2556 */
2557 if (region == tiny_mag_ptr->mag_last_region)
2558 region_end -= tiny_mag_ptr->mag_bytes_free_at_end;
2559
2560 /*
2561 * Scan blocks within the region.
2562 */
2563 while (ptr < region_end) {
2564 /*
2565 * If the first block is free, and its size is 65536 (msize = 0) then the entire region is
2566 * free.
2567 */
2568 msize = get_tiny_meta_header((void *)ptr, &is_free);
2569 if (is_free && !msize && (ptr == start)) {
2570 return 1;
2571 }
2572
2573 /*
2574 * If the block's size is 65536 (msize = 0) then since we're not the first entry the size is
2575 * corrupt.
2576 */
2577 if (!msize) {
2578 malloc_printf("*** invariant broken for tiny block %p this msize=%d - size is too small\n",
2579 ptr, msize);
2580 return 0;
2581 }
2582
2583 if (!is_free) {
2584 /*
2585 * In use blocks cannot be more than (NUM_TINY_SLOTS - 1) quanta large.
2586 */
2587 prev_free = 0;
2588 if (msize > (NUM_TINY_SLOTS - 1)) {
2589 malloc_printf("*** invariant broken for %p this tiny msize=%d - size is too large\n",
2590 ptr, msize);
2591 return 0;
2592 }
2593 /* move to next block */
2594 ptr += TINY_BYTES_FOR_MSIZE(msize);
2595 } else {
2596 /*
2597 * Free blocks must have been coalesced, we cannot have a free block following another
2598 * free block.
2599 */
2600 if (prev_free) {
2601 malloc_printf("*** invariant broken for free block %p this tiny msize=%d: two free blocks in a row\n",
2602 ptr, msize);
2603 return 0;
2604 }
2605 prev_free = 1;
2606 /*
2607 * Check the integrity of this block's entry in its freelist.
2608 */
2609 free_head = (free_list_t *)ptr;
2610 previous = free_list_unchecksum_ptr(szone, &free_head->previous);
2611 next = free_list_unchecksum_ptr(szone, &free_head->next);
2612 if (previous && !tiny_meta_header_is_free(previous)) {
2613 malloc_printf("*** invariant broken for %p (previous %p is not a free pointer)\n",
2614 ptr, previous);
2615 return 0;
2616 }
2617 if (next && !tiny_meta_header_is_free(next)) {
2618 malloc_printf("*** invariant broken for %p (next in free list %p is not a free pointer)\n",
2619 ptr, next);
2620 return 0;
2621 }
2622 /*
2623 * Check the free block's trailing size value.
2624 */
2625 follower = FOLLOWING_TINY_PTR(ptr, msize);
2626 if (((uintptr_t)follower != region_end) && (get_tiny_previous_free_msize(follower) != msize)) {
2627 malloc_printf("*** invariant broken for tiny free %p followed by %p in region [%p-%p] "
2628 "(end marker incorrect) should be %d; in fact %d\n",
2629 ptr, follower, TINY_REGION_ADDRESS(region), region_end, msize, get_tiny_previous_free_msize(follower));
2630 return 0;
2631 }
2632 /* move to next block */
2633 ptr = (uintptr_t)follower;
2634 }
2635 }
2636 /*
2637 * Ensure that we scanned the entire region
2638 */
2639 if (ptr != region_end) {
2640 malloc_printf("*** invariant broken for region end %p - %p\n", ptr, region_end);
2641 return 0;
2642 }
2643 /*
2644 * Check the trailing block's integrity.
2645 */
2646 if (region == tiny_mag_ptr->mag_last_region) {
2647 if (tiny_mag_ptr->mag_bytes_free_at_end) {
2648 msize = get_tiny_meta_header((void *)ptr, &is_free);
2649 if (is_free || (msize != 1)) {
2650 malloc_printf("*** invariant broken for blocker block %p - %d %d\n", ptr, msize, is_free);
2651 }
2652 }
2653 }
2654 return 1;
2655 }
2656
2657 static kern_return_t
2658 tiny_in_use_enumerator(task_t task, void *context, unsigned type_mask, szone_t *szone,
2659 memory_reader_t reader, vm_range_recorder_t recorder)
2660 {
2661 size_t num_regions;
2662 size_t index;
2663 region_t *regions;
2664 vm_range_t buffer[MAX_RECORDER_BUFFER];
2665 unsigned count = 0;
2666 kern_return_t err;
2667 region_t region;
2668 vm_range_t range;
2669 vm_range_t admin_range;
2670 vm_range_t ptr_range;
2671 unsigned char *mapped_region;
2672 uint32_t *block_header;
2673 uint32_t *in_use;
2674 unsigned block_index;
2675 unsigned block_limit;
2676 boolean_t is_free;
2677 msize_t msize;
2678 void *mapped_ptr;
2679 unsigned bit;
2680 vm_address_t mag_last_free_ptr = 0;
2681 msize_t mag_last_free_msize = 0;
2682
2683 region_hash_generation_t *trg_ptr;
2684 err = reader(task, (vm_address_t)szone->tiny_region_generation, sizeof(region_hash_generation_t), (void **)&trg_ptr);
2685 if (err) return err;
2686
2687 num_regions = trg_ptr->num_regions_allocated;
2688 err = reader(task, (vm_address_t)trg_ptr->hashed_regions, sizeof(region_t) * num_regions, (void **)&regions);
2689 if (err) return err;
2690
2691 for (index = 0; index < num_regions; ++index) {
2692 region = regions[index];
2693 if (HASHRING_OPEN_ENTRY != region && HASHRING_REGION_DEALLOCATED != region) {
2694 range.address = (vm_address_t)TINY_REGION_ADDRESS(region);
2695 range.size = (vm_size_t)TINY_REGION_SIZE;
2696 if (type_mask & MALLOC_ADMIN_REGION_RANGE_TYPE) {
2697 admin_range.address = range.address + TINY_METADATA_START;
2698 admin_range.size = TINY_METADATA_SIZE;
2699 recorder(task, context, MALLOC_ADMIN_REGION_RANGE_TYPE, &admin_range, 1);
2700 }
2701 if (type_mask & (MALLOC_PTR_REGION_RANGE_TYPE | MALLOC_ADMIN_REGION_RANGE_TYPE)) {
2702 ptr_range.address = range.address;
2703 ptr_range.size = NUM_TINY_BLOCKS * TINY_QUANTUM;
2704 recorder(task, context, MALLOC_PTR_REGION_RANGE_TYPE, &ptr_range, 1);
2705 }
2706 if (type_mask & MALLOC_PTR_IN_USE_RANGE_TYPE) {
2707 err = reader(task, range.address, range.size, (void **)&mapped_region);
2708 if (err)
2709 return err;
2710
2711 mag_index_t mag_index = MAGAZINE_INDEX_FOR_TINY_REGION(mapped_region);
2712 magazine_t *tiny_mag_ptr;
2713 err = reader(task, (vm_address_t)&(szone->tiny_magazines[mag_index]), sizeof(magazine_t),
2714 (void **)&tiny_mag_ptr);
2715 if (err) return err;
2716
2717 void *mag_last_free = tiny_mag_ptr->mag_last_free;
2718 if (mag_last_free) {
2719 mag_last_free_ptr = (uintptr_t) mag_last_free & ~(TINY_QUANTUM - 1);
2720 mag_last_free_msize = (uintptr_t) mag_last_free & (TINY_QUANTUM - 1);
2721 }
2722
2723 block_header = (uint32_t *)(mapped_region + TINY_METADATA_START + sizeof(region_trailer_t));
2724 in_use = TINY_INUSE_FOR_HEADER(block_header);
2725 block_index = 0;
2726 block_limit = NUM_TINY_BLOCKS;
2727 if (region == tiny_mag_ptr->mag_last_region)
2728 block_limit -= TINY_MSIZE_FOR_BYTES(tiny_mag_ptr->mag_bytes_free_at_end);
2729
2730 while (block_index < block_limit) {
2731 vm_size_t block_offset = TINY_BYTES_FOR_MSIZE(block_index);
2732 is_free = !BITARRAY_BIT(in_use, block_index);
2733 if (is_free) {
2734 mapped_ptr = mapped_region + block_offset;
2735
2736 // mapped_region, the address at which 'range' in 'task' has been
2737 // mapped into our process, is not necessarily aligned to
2738 // TINY_BLOCKS_ALIGN.
2739 //
2740 // Since the code in get_tiny_free_size() assumes the pointer came
2741 // from a properly aligned tiny region, and mapped_region is not
2742 // necessarily aligned, then do the size calculation directly.
2743 // If the next bit is set in the header bitmap, then the size is one
2744 // quantum. Otherwise, read the size field.
2745 if (!BITARRAY_BIT(block_header, (block_index+1)))
2746 msize = TINY_FREE_SIZE(mapped_ptr);
2747 else
2748 msize = 1;
2749
2750 if (!msize)
2751 break;
2752 } else if (range.address + block_offset != mag_last_free_ptr) {
2753 msize = 1;
2754 bit = block_index + 1;
2755 while (! BITARRAY_BIT(block_header, bit)) {
2756 bit++;
2757 msize ++;
2758 }
2759 buffer[count].address = range.address + block_offset;
2760 buffer[count].size = TINY_BYTES_FOR_MSIZE(msize);
2761 count++;
2762 if (count >= MAX_RECORDER_BUFFER) {
2763 recorder(task, context, MALLOC_PTR_IN_USE_RANGE_TYPE, buffer, count);
2764 count = 0;
2765 }
2766 } else {
2767 // Block is not free but it matches mag_last_free_ptr so even
2768 // though it is not marked free in the bitmap, we treat it as if
2769 // it is and move on
2770 msize = mag_last_free_msize;
2771 }
2772 block_index += msize;
2773 }
2774 if (count) {
2775 recorder(task, context, MALLOC_PTR_IN_USE_RANGE_TYPE, buffer, count);
2776 count = 0;
2777 }
2778 }
2779 }
2780 }
2781 return 0;
2782 }
2783
2784 static void *
2785 tiny_malloc_from_free_list(szone_t *szone, magazine_t *tiny_mag_ptr, mag_index_t mag_index, msize_t msize)
2786 {
2787 free_list_t *ptr;
2788 msize_t this_msize;
2789 grain_t slot = msize - 1;
2790 free_list_t **free_list = tiny_mag_ptr->mag_free_list;
2791 free_list_t **the_slot = free_list + slot;
2792 free_list_t *next;
2793 free_list_t **limit;
2794 #if defined(__LP64__)
2795 uint64_t bitmap;
2796 #else
2797 uint32_t bitmap;
2798 #endif
2799 msize_t leftover_msize;
2800 free_list_t *leftover_ptr;
2801
2802 // Assumes we've locked the region
2803 CHECK_MAGAZINE_PTR_LOCKED(szone, tiny_mag_ptr, __PRETTY_FUNCTION__);
2804
2805 // Look for an exact match by checking the freelist for this msize.
2806 //
2807 ptr = *the_slot;
2808 if (ptr) {
2809 next = free_list_unchecksum_ptr(szone, &ptr->next);
2810 if (next) {
2811 next->previous = ptr->previous;
2812 } else {
2813 BITMAPV_CLR(tiny_mag_ptr->mag_bitmap, slot);
2814 }
2815 *the_slot = next;
2816 this_msize = msize;
2817 #if DEBUG_MALLOC
2818 if (LOG(szone, ptr)) {
2819 malloc_printf("in tiny_malloc_from_free_list(), exact match ptr=%p, this_msize=%d\n", ptr, this_msize);
2820 }
2821 #endif
2822 goto return_tiny_alloc;
2823 }
2824
2825 // Mask off the bits representing slots holding free blocks smaller than the
2826 // size we need. If there are no larger free blocks, try allocating from
2827 // the free space at the end of the tiny region.
2828 #if defined(__LP64__)
2829 bitmap = ((uint64_t *)(tiny_mag_ptr->mag_bitmap))[0] & ~ ((1ULL << slot) - 1);
2830 #else
2831 bitmap = tiny_mag_ptr->mag_bitmap[0] & ~ ((1 << slot) - 1);
2832 #endif
2833 if (!bitmap)
2834 goto try_tiny_malloc_from_end;
2835
2836 slot = BITMAPV_CTZ(bitmap);
2837 limit = free_list + NUM_TINY_SLOTS - 1;
2838 free_list += slot;
2839
2840 if (free_list < limit) {
2841 ptr = *free_list;
2842 if (ptr) {
2843 next = free_list_unchecksum_ptr(szone, &ptr->next);
2844 *free_list = next;
2845 if (next) {
2846 next->previous = ptr->previous;
2847 } else {
2848 BITMAPV_CLR(tiny_mag_ptr->mag_bitmap, slot);
2849 }
2850 this_msize = get_tiny_free_size(ptr);
2851 goto add_leftover_and_proceed;
2852 }
2853 #if DEBUG_MALLOC
2854 malloc_printf("in tiny_malloc_from_free_list(), mag_bitmap out of sync, slot=%d\n",slot);
2855 #endif
2856 }
2857
2858 // We are now looking at the last slot, which contains blocks equal to, or
2859 // due to coalescing of free blocks, larger than (NUM_TINY_SLOTS - 1) * tiny quantum size.
2860 // If the last freelist is not empty, and the head contains a block that is
2861 // larger than our request, then the remainder is put back on the free list.
2862 ptr = *limit;
2863 if (ptr) {
2864 this_msize = get_tiny_free_size(ptr);
2865 next = free_list_unchecksum_ptr(szone, &ptr->next);
2866 if (this_msize - msize >= NUM_TINY_SLOTS) {
2867 // the leftover will go back to the free list, so we optimize by
2868 // modifying the free list rather than a pop and push of the head
2869 leftover_msize = this_msize - msize;
2870 leftover_ptr = (free_list_t *)((unsigned char *)ptr + TINY_BYTES_FOR_MSIZE(msize));
2871 *limit = leftover_ptr;
2872 if (next) {
2873 next->previous.u = free_list_checksum_ptr(szone, leftover_ptr);
2874 }
2875 leftover_ptr->previous = ptr->previous;
2876 leftover_ptr->next = ptr->next;
2877 set_tiny_meta_header_free(leftover_ptr, leftover_msize);
2878 #if DEBUG_MALLOC
2879 if (LOG(szone,ptr)) {
2880 malloc_printf("in tiny_malloc_from_free_list(), last slot ptr=%p, msize=%d this_msize=%d\n",
2881 ptr, msize, this_msize);
2882 }
2883 #endif
2884 this_msize = msize;
2885 goto return_tiny_alloc;
2886 }
2887 if (next) {
2888 next->previous = ptr->previous;
2889 }
2890 *limit = next;
2891 goto add_leftover_and_proceed;
2892 /* NOTREACHED */
2893 }
2894
2895 try_tiny_malloc_from_end:
2896 // Let's see if we can use tiny_mag_ptr->mag_bytes_free_at_end
2897 if (tiny_mag_ptr->mag_bytes_free_at_end >= TINY_BYTES_FOR_MSIZE(msize)) {
2898 ptr = (free_list_t *)((uintptr_t)TINY_REGION_END(tiny_mag_ptr->mag_last_region) -
2899 tiny_mag_ptr->mag_bytes_free_at_end);
2900 tiny_mag_ptr->mag_bytes_free_at_end -= TINY_BYTES_FOR_MSIZE(msize);
2901 if (tiny_mag_ptr->mag_bytes_free_at_end) {
2902 // let's add an in use block after ptr to serve as boundary
2903 set_tiny_meta_header_in_use_1((unsigned char *)ptr + TINY_BYTES_FOR_MSIZE(msize));
2904 }
2905 this_msize = msize;
2906 #if DEBUG_MALLOC
2907 if (LOG(szone, ptr)) {
2908 malloc_printf("in tiny_malloc_from_free_list(), from end ptr=%p, msize=%d\n", ptr, msize);
2909 }
2910 #endif
2911 goto return_tiny_alloc;
2912 }
2913 return NULL;
2914
2915 add_leftover_and_proceed:
2916 if (!this_msize || (this_msize > msize)) {
2917 leftover_msize = this_msize - msize;
2918 leftover_ptr = (free_list_t *)((unsigned char *)ptr + TINY_BYTES_FOR_MSIZE(msize));
2919 #if DEBUG_MALLOC
2920 if (LOG(szone,ptr)) {
2921 malloc_printf("in tiny_malloc_from_free_list(), adding leftover ptr=%p, this_msize=%d\n", ptr, this_msize);
2922 }
2923 #endif
2924 tiny_free_list_add_ptr(szone, tiny_mag_ptr, leftover_ptr, leftover_msize);
2925 this_msize = msize;
2926 }
2927
2928 return_tiny_alloc:
2929 tiny_mag_ptr->mag_num_objects++;
2930 tiny_mag_ptr->mag_num_bytes_in_objects += TINY_BYTES_FOR_MSIZE(this_msize);
2931
2932 // Update this region's bytes in use count
2933 region_trailer_t *node = REGION_TRAILER_FOR_TINY_REGION(TINY_REGION_FOR_PTR(ptr));
2934 size_t bytes_used = node->bytes_used + TINY_BYTES_FOR_MSIZE(this_msize);
2935 node->bytes_used = bytes_used;
2936
2937 // Emptiness discriminant
2938 if (bytes_used < DENSITY_THRESHOLD(TINY_REGION_PAYLOAD_BYTES)) {
2939 /* After this allocation the region is still sparse, so it must have been even more so before
2940 the allocation. That implies the region is already correctly marked. Do nothing. */
2941 } else {
2942 /* Region has crossed threshold from sparsity to density. Mark it not "suitable" on the
2943 recirculation candidates list. */
2944 node->recirc_suitable = FALSE;
2945 }
2946 #if DEBUG_MALLOC
2947 if (LOG(szone,ptr)) {
2948 malloc_printf("in tiny_malloc_from_free_list(), ptr=%p, this_msize=%d, msize=%d\n", ptr, this_msize, msize);
2949 }
2950 #endif
2951 if (this_msize > 1)
2952 set_tiny_meta_header_in_use(ptr, this_msize);
2953 else
2954 set_tiny_meta_header_in_use_1(ptr);
2955 return ptr;
2956 }
2957 #undef DENSITY_THRESHOLD
2958 #undef K
2959
2960 static INLINE void *
2961 tiny_malloc_should_clear(szone_t *szone, msize_t msize, boolean_t cleared_requested)
2962 {
2963 void *ptr;
2964 mag_index_t mag_index = mag_get_thread_index(szone);
2965 magazine_t *tiny_mag_ptr = &(szone->tiny_magazines[mag_index]);
2966
2967 #if DEBUG_MALLOC
2968 if (DEPOT_MAGAZINE_INDEX == mag_index) {
2969 szone_error(szone, 1, "malloc called for magazine index -1", NULL, NULL);
2970 return(NULL);
2971 }
2972
2973 if (!msize) {
2974 szone_error(szone, 1, "invariant broken (!msize) in allocation (region)", NULL, NULL);
2975 return(NULL);
2976 }
2977 #endif
2978
2979 SZONE_MAGAZINE_PTR_LOCK(szone, tiny_mag_ptr);
2980
2981 #if TINY_CACHE
2982 ptr = tiny_mag_ptr->mag_last_free;
2983
2984 if ((((uintptr_t)ptr) & (TINY_QUANTUM - 1)) == msize) {
2985 // we have a winner
2986 tiny_mag_ptr->mag_last_free = NULL;
2987 SZONE_MAGAZINE_PTR_UNLOCK(szone, tiny_mag_ptr);
2988 CHECK(szone, __PRETTY_FUNCTION__);
2989 ptr = (void *)((uintptr_t)ptr & ~ (TINY_QUANTUM - 1));
2990 if (cleared_requested) {
2991 memset(ptr, 0, TINY_BYTES_FOR_MSIZE(msize));
2992 }
2993 #if DEBUG_MALLOC
2994 if (LOG(szone,ptr)) {
2995 malloc_printf("in tiny_malloc_should_clear(), tiny cache ptr=%p, msize=%d\n", ptr, msize);
2996 }
2997 #endif
2998 return ptr;
2999 }
3000 #endif /* TINY_CACHE */
3001
3002 ptr = tiny_malloc_from_free_list(szone, tiny_mag_ptr, mag_index, msize);
3003 if (ptr) {
3004 SZONE_MAGAZINE_PTR_UNLOCK(szone, tiny_mag_ptr);
3005 CHECK(szone, __PRETTY_FUNCTION__);
3006 if (cleared_requested) {
3007 memset(ptr, 0, TINY_BYTES_FOR_MSIZE(msize));
3008 }
3009 return ptr;
3010 }
3011
3012 if (tiny_get_region_from_depot(szone, tiny_mag_ptr, mag_index)) {
3013 ptr = tiny_malloc_from_free_list(szone, tiny_mag_ptr, mag_index, msize);
3014 if (ptr) {
3015 SZONE_MAGAZINE_PTR_UNLOCK(szone, tiny_mag_ptr);
3016 CHECK(szone, __PRETTY_FUNCTION__);
3017 if (cleared_requested) {
3018 memset(ptr, 0, TINY_BYTES_FOR_MSIZE(msize));
3019 }
3020 return ptr;
3021 }
3022 }
3023
3024 ptr = tiny_malloc_from_region_no_lock(szone, tiny_mag_ptr, mag_index, msize);
3025 // we don't clear because this freshly allocated space is pristine
3026 SZONE_MAGAZINE_PTR_UNLOCK(szone, tiny_mag_ptr);
3027 CHECK(szone, __PRETTY_FUNCTION__);
3028 return ptr;
3029 }
3030
3031 static NOINLINE void
3032 free_tiny_botch(szone_t *szone, free_list_t *ptr)
3033 {
3034 mag_index_t mag_index = MAGAZINE_INDEX_FOR_TINY_REGION(TINY_REGION_FOR_PTR(ptr));
3035 magazine_t *tiny_mag_ptr = &(szone->tiny_magazines[mag_index]);
3036 SZONE_MAGAZINE_PTR_UNLOCK(szone, tiny_mag_ptr);
3037 szone_error(szone, 1, "double free", ptr, NULL);
3038 }
3039
3040 static INLINE void
3041 free_tiny(szone_t *szone, void *ptr, region_t tiny_region, size_t known_size)
3042 {
3043 msize_t msize;
3044 boolean_t is_free;
3045 mag_index_t mag_index = MAGAZINE_INDEX_FOR_TINY_REGION(tiny_region);
3046 magazine_t *tiny_mag_ptr = &(szone->tiny_magazines[mag_index]);
3047
3048 // ptr is known to be in tiny_region
3049 if (known_size) {
3050 msize = TINY_MSIZE_FOR_BYTES(known_size + TINY_QUANTUM - 1);
3051 } else {
3052 msize = get_tiny_meta_header(ptr, &is_free);
3053 if (is_free) {
3054 free_tiny_botch(szone, ptr);
3055 return;
3056 }
3057 }
3058 #if DEBUG_MALLOC
3059 if (!msize) {
3060 malloc_printf("*** free_tiny() block in use is too large: %p\n", ptr);
3061 return;
3062 }
3063 #endif
3064
3065 SZONE_MAGAZINE_PTR_LOCK(szone, tiny_mag_ptr);
3066
3067 #if TINY_CACHE
3068 // Depot does not participate in TINY_CACHE since it can't be directly malloc()'d
3069 if (DEPOT_MAGAZINE_INDEX != mag_index) {
3070 if (msize < TINY_QUANTUM) { // to see if the bits fit in the last 4 bits
3071 void *ptr2 = tiny_mag_ptr->mag_last_free; // Might be NULL
3072 region_t rgn2 = tiny_mag_ptr->mag_last_free_rgn;
3073
3074 /* check that we don't already have this pointer in the cache */
3075 if (ptr == (void *)((uintptr_t)ptr2 & ~ (TINY_QUANTUM - 1))) {
3076 free_tiny_botch(szone, ptr);
3077 return;
3078 }
3079
3080 if ((szone->debug_flags & SCALABLE_MALLOC_DO_SCRIBBLE) && msize)
3081 memset(ptr, 0x55, TINY_BYTES_FOR_MSIZE(msize));
3082
3083 tiny_mag_ptr->mag_last_free = (void *)(((uintptr_t)ptr) | msize);
3084 tiny_mag_ptr->mag_last_free_rgn = tiny_region;
3085
3086 if (!ptr2) {
3087 SZONE_MAGAZINE_PTR_UNLOCK(szone, tiny_mag_ptr);
3088 CHECK(szone, __PRETTY_FUNCTION__);
3089 return;
3090 }
3091
3092 msize = (uintptr_t)ptr2 & (TINY_QUANTUM - 1);
3093 ptr = (void *)(((uintptr_t)ptr2) & ~(TINY_QUANTUM - 1));
3094 tiny_region = rgn2;
3095 }
3096 }
3097 #endif /* TINY_CACHE */
3098
3099 // Now in the time it took to acquire the lock, the region may have migrated
3100 // from one magazine to another. I.e. trailer->mag_index is volatile.
3101 // In which case the magazine lock we obtained (namely magazines[mag_index].mag_lock)
3102 // is stale. If so, keep on tryin' ...
3103 region_trailer_t *trailer = REGION_TRAILER_FOR_TINY_REGION(tiny_region);
3104 mag_index_t refreshed_index;
3105
3106 while (mag_index != (refreshed_index = trailer->mag_index)) { // Note assignment
3107
3108 SZONE_MAGAZINE_PTR_UNLOCK(szone, tiny_mag_ptr);
3109
3110 mag_index = refreshed_index;
3111 tiny_mag_ptr = &(szone->tiny_magazines[mag_index]);
3112 SZONE_MAGAZINE_PTR_LOCK(szone, tiny_mag_ptr);
3113 }
3114
3115 tiny_free_no_lock(szone, tiny_mag_ptr, mag_index, tiny_region, ptr, msize);
3116 SZONE_MAGAZINE_PTR_UNLOCK(szone, tiny_mag_ptr);
3117 CHECK(szone, __PRETTY_FUNCTION__);
3118 }
3119
3120 static void
3121 print_tiny_free_list(szone_t *szone)
3122 {
3123 free_list_t *ptr;
3124 _SIMPLE_STRING b = _simple_salloc();
3125 mag_index_t mag_index;
3126
3127 if (b) {
3128 _simple_sappend(b, "tiny free sizes:\n");
3129 for (mag_index = -1; mag_index < szone->num_tiny_magazines; mag_index++) {
3130 grain_t slot = 0;
3131 _simple_sprintf(b,"\tMagazine %d: ", mag_index);
3132 while (slot < NUM_TINY_SLOTS) {
3133 ptr = szone->tiny_magazines[mag_index].mag_free_list[slot];
3134 if (ptr) {
3135 _simple_sprintf(b, "%s%y[%d]; ", (slot == NUM_TINY_SLOTS-1) ? ">=" : "",
3136 (slot+1)*TINY_QUANTUM, free_list_count(szone, ptr));
3137 }
3138 slot++;
3139 }
3140 _simple_sappend(b,"\n");
3141 }
3142 _malloc_printf(MALLOC_PRINTF_NOLOG | MALLOC_PRINTF_NOPREFIX, "%s\n", _simple_string(b));
3143 _simple_sfree(b);
3144 }
3145 }
3146
3147 static void
3148 print_tiny_region(boolean_t verbose, region_t region, size_t bytes_at_end)
3149 {
3150 unsigned counts[1024];
3151 unsigned in_use = 0;
3152 uintptr_t start = (uintptr_t)TINY_REGION_ADDRESS(region);
3153 uintptr_t current = start;
3154 uintptr_t limit = (uintptr_t)TINY_REGION_END(region) - bytes_at_end;
3155 boolean_t is_free;
3156 msize_t msize;
3157 unsigned ci;
3158 _SIMPLE_STRING b;
3159 uintptr_t pgTot = 0;
3160
3161 if (region == HASHRING_REGION_DEALLOCATED) {
3162 if ((b = _simple_salloc()) != NULL) {
3163 _simple_sprintf(b, "Tiny region [unknown address] was returned to the OS\n");
3164 _malloc_printf(MALLOC_PRINTF_NOLOG | MALLOC_PRINTF_NOPREFIX, "%s\n", _simple_string(b));
3165 _simple_sfree(b);
3166 }
3167 return;
3168 }
3169
3170 memset(counts, 0, sizeof(counts));
3171 while (current < limit) {
3172 msize = get_tiny_meta_header((void *)current, &is_free);
3173 if (is_free & !msize && (current == start)) {
3174 // first block is all free
3175 uintptr_t pgLo = round_page(start + sizeof(free_list_t) + sizeof(msize_t));
3176 uintptr_t pgHi = trunc_page(start + TINY_REGION_SIZE - sizeof(msize_t));
3177
3178 if (pgLo < pgHi) {
3179 pgTot += (pgHi - pgLo);
3180 }
3181 break;
3182 }
3183 if (!msize) {
3184 malloc_printf("*** error with %p: msize=%d\n", (void *)current, (unsigned)msize);
3185 break;
3186 }
3187 if (!is_free) {
3188 // block in use
3189 if (msize > NUM_TINY_SLOTS)
3190 malloc_printf("*** error at %p msize for in_use is %d\n", (void *)current, msize);
3191 if (msize < 1024)
3192 counts[msize]++;
3193 in_use++;
3194 } else {
3195 uintptr_t pgLo = round_page(current + sizeof(free_list_t) + sizeof(msize_t));
3196 uintptr_t pgHi = trunc_page(current + TINY_BYTES_FOR_MSIZE(msize) - sizeof(msize_t));
3197
3198 if (pgLo < pgHi) {
3199 pgTot += (pgHi - pgLo);
3200 }
3201 }
3202 current += TINY_BYTES_FOR_MSIZE(msize);
3203 }
3204 if ((b = _simple_salloc()) != NULL) {
3205 _simple_sprintf(b, "Tiny region [%p-%p, %y] \t", (void *)start, TINY_REGION_END(region), (int)TINY_REGION_SIZE);
3206 _simple_sprintf(b, "Magazine=%d \t", MAGAZINE_INDEX_FOR_TINY_REGION(region));
3207 _simple_sprintf(b, "Allocations in use=%d \t Bytes in use=%ly \t", in_use, BYTES_USED_FOR_TINY_REGION(region));
3208 if (bytes_at_end)
3209 _simple_sprintf(b, "Untouched=%ly ", bytes_at_end);
3210 if (DEPOT_MAGAZINE_INDEX == MAGAZINE_INDEX_FOR_TINY_REGION(region)) {
3211 _simple_sprintf(b, "Advised MADV_FREE=%ly", pgTot);
3212 } else {
3213 _simple_sprintf(b, "Fragments subject to reclamation=%ly", pgTot);
3214 }
3215 if (verbose && in_use) {
3216 _simple_sappend(b, "\n\tSizes in use: ");
3217 for (ci = 0; ci < 1024; ci++)
3218 if (counts[ci])
3219 _simple_sprintf(b, "%d[%d] ", TINY_BYTES_FOR_MSIZE(ci), counts[ci]);
3220 }
3221 _malloc_printf(MALLOC_PRINTF_NOLOG | MALLOC_PRINTF_NOPREFIX, "%s\n", _simple_string(b));
3222 _simple_sfree(b);
3223 }
3224 }
3225
3226 static boolean_t
3227 tiny_free_list_check(szone_t *szone, grain_t slot)
3228 {
3229 mag_index_t mag_index;
3230
3231 for (mag_index = -1; mag_index < szone->num_tiny_magazines; mag_index++) {
3232 magazine_t *tiny_mag_ptr = &(szone->tiny_magazines[mag_index]);
3233 SZONE_MAGAZINE_PTR_LOCK(szone, tiny_mag_ptr);
3234
3235 unsigned count = 0;
3236 free_list_t *ptr = szone->tiny_magazines[mag_index].mag_free_list[slot];
3237 boolean_t is_free;
3238 free_list_t *previous = NULL;
3239
3240 while (ptr) {
3241 is_free = tiny_meta_header_is_free(ptr);
3242 if (! is_free) {
3243 malloc_printf("*** in-use ptr in free list slot=%d count=%d ptr=%p\n", slot, count, ptr);
3244 SZONE_MAGAZINE_PTR_UNLOCK(szone, tiny_mag_ptr);
3245 return 0;
3246 }
3247 if (((uintptr_t)ptr) & (TINY_QUANTUM - 1)) {
3248 malloc_printf("*** unaligned ptr in free list slot=%d count=%d ptr=%p\n", slot, count, ptr);
3249 SZONE_MAGAZINE_PTR_UNLOCK(szone, tiny_mag_ptr);
3250 return 0;
3251 }
3252 if (!tiny_region_for_ptr_no_lock(szone, ptr)) {
3253 malloc_printf("*** ptr not in szone slot=%d count=%d ptr=%p\n", slot, count, ptr);
3254 SZONE_MAGAZINE_PTR_UNLOCK(szone, tiny_mag_ptr);
3255 return 0;
3256 }
3257 if (free_list_unchecksum_ptr(szone, &ptr->previous) != previous) {
3258 malloc_printf("*** previous incorrectly set slot=%d count=%d ptr=%p\n", slot, count, ptr);
3259 SZONE_MAGAZINE_PTR_UNLOCK(szone, tiny_mag_ptr);
3260 return 0;
3261 }
3262 previous = ptr;
3263 ptr = free_list_unchecksum_ptr(szone, &ptr->next);
3264 count++;
3265 }
3266
3267 SZONE_MAGAZINE_PTR_UNLOCK(szone, tiny_mag_ptr);
3268 }
3269 return 1;
3270 }
3271
3272 /********************* SMALL FREE LIST UTILITIES ************************/
3273
3274 /*
3275 * Mark a block as free. Only the first quantum of a block is marked thusly,
3276 * the remainder are marked "middle".
3277 */
3278 static INLINE void
3279 small_meta_header_set_is_free(msize_t *meta_headers, unsigned index, msize_t msize)
3280 {
3281 meta_headers[index] = msize | SMALL_IS_FREE;
3282 }
3283
3284 /*
3285 * Mark a block as in use. Only the first quantum of a block is marked thusly,
3286 * the remainder are marked "middle".
3287 */
3288 static INLINE void
3289 small_meta_header_set_in_use(msize_t *meta_headers, msize_t index, msize_t msize)
3290 {
3291 meta_headers[index] = msize;
3292 }
3293
3294 /*
3295 * Mark a quantum as being the second or later in a block.
3296 */
3297 static INLINE void
3298 small_meta_header_set_middle(msize_t *meta_headers, msize_t index)
3299 {
3300 meta_headers[index] = 0;
3301 }
3302
3303 /*
3304 * Adds an item to the proper free list, and also marks the meta-header of the
3305 * block properly.
3306 * Assumes szone has been locked
3307 */
3308 static void
3309 small_free_list_add_ptr(szone_t *szone, magazine_t *small_mag_ptr, void *ptr, msize_t msize)
3310 {
3311 grain_t slot = (msize <= szone->num_small_slots) ? msize - 1 : szone->num_small_slots - 1;
3312 free_list_t *free_ptr = ptr;
3313 free_list_t *free_head = small_mag_ptr->mag_free_list[slot];
3314 void *follower;
3315
3316 #if DEBUG_MALLOC
3317 if (LOG(szone,ptr)) {
3318 malloc_printf("in %s, ptr=%p, msize=%d\n", __FUNCTION__, ptr, msize);
3319 }
3320 if (((uintptr_t)ptr) & (SMALL_QUANTUM - 1)) {
3321 szone_error(szone, 1, "small_free_list_add_ptr: Unaligned ptr", ptr, NULL);
3322 }
3323 #endif
3324 small_meta_header_set_is_free(SMALL_META_HEADER_FOR_PTR(ptr), SMALL_META_INDEX_FOR_PTR(ptr), msize);
3325
3326 if (free_head) {
3327 #if DEBUG_MALLOC
3328 if (free_list_unchecksum_ptr(szone, &free_head->previous)) {
3329 szone_error(szone, 1, "small_free_list_add_ptr: Internal invariant broken (free_head->previous)", ptr,
3330 "ptr=%p slot=%d free_head=%p previous=%p\n", ptr, slot, (void *)free_head, free_head->previous.p);
3331 }
3332 if (!SMALL_PTR_IS_FREE(free_head)) {
3333 szone_error(szone, 1, "small_free_list_add_ptr: Internal invariant broken (free_head is not a free pointer)", ptr,
3334 "ptr=%p slot=%d free_head=%p\n", ptr, slot, (void *)free_head);
3335 }
3336 #endif
3337 free_head->previous.u = free_list_checksum_ptr(szone, free_ptr);
3338 } else {
3339 BITMAPN_SET(small_mag_ptr->mag_bitmap, slot);
3340 }
3341 free_ptr->previous.u = free_list_checksum_ptr(szone, NULL);
3342 free_ptr->next.u = free_list_checksum_ptr(szone, free_head);
3343
3344 small_mag_ptr->mag_free_list[slot] = free_ptr;
3345
3346 // Store msize at the end of the block denoted by "ptr" (i.e. at a negative offset from "follower")
3347 follower = (void *)((uintptr_t)ptr + SMALL_BYTES_FOR_MSIZE(msize));
3348 SMALL_PREVIOUS_MSIZE(follower) = msize;
3349 }
3350
3351 /*
3352 * Removes the item pointed to by ptr in the proper free list.
3353 * Assumes szone has been locked
3354 */
3355 static void
3356 small_free_list_remove_ptr(szone_t *szone, magazine_t *small_mag_ptr, void *ptr, msize_t msize)
3357 {
3358 grain_t slot = (msize <= szone->num_small_slots) ? msize - 1 : szone->num_small_slots - 1;
3359 free_list_t *free_ptr = ptr, *next, *previous;
3360
3361 next = free_list_unchecksum_ptr(szone, &free_ptr->next);
3362 previous = free_list_unchecksum_ptr(szone, &free_ptr->previous);
3363
3364 #if DEBUG_MALLOC
3365 if (LOG(szone,ptr)) {
3366 malloc_printf("In %s, ptr=%p, msize=%d\n", __FUNCTION__, ptr, msize);
3367 }
3368 #endif
3369
3370 if (!previous) {
3371 // The block to remove is the head of the free list
3372 #if DEBUG_MALLOC
3373 if (small_mag_ptr->mag_free_list[slot] != ptr) {
3374 szone_error(szone, 1, "small_free_list_remove_ptr: Internal invariant broken (small_mag_ptr->mag_free_list[slot])", ptr,
3375 "ptr=%p slot=%d msize=%d small_mag_ptr->mag_free_list[slot]=%p\n",
3376 ptr, slot, msize, (void *)small_mag_ptr->mag_free_list[slot]);
3377 return;
3378 }
3379 #endif
3380 small_mag_ptr->mag_free_list[slot] = next;
3381 if (!next) BITMAPN_CLR(small_mag_ptr->mag_bitmap, slot);
3382 } else {
3383 // We know free_ptr is already checksummed, so we don't need to do it
3384 // again.
3385 previous->next = free_ptr->next;
3386 }
3387 if (next) {
3388 // We know free_ptr is already checksummed, so we don't need to do it
3389 // again.
3390 next->previous = free_ptr->previous;
3391 }
3392 }
3393
3394 /*
3395 * small_region_for_ptr_no_lock - Returns the small region containing the pointer,
3396 * or NULL if not found.
3397 */
3398 static INLINE region_t
3399 small_region_for_ptr_no_lock(szone_t *szone, const void *ptr)
3400 {
3401 rgnhdl_t r = hash_lookup_region_no_lock(szone->small_region_generation->hashed_regions,
3402 szone->small_region_generation->num_regions_allocated,
3403 szone->small_region_generation->num_regions_allocated_shift,
3404 SMALL_REGION_FOR_PTR(ptr));
3405 return r ? *r : r;
3406 }
3407
3408 static void
3409 small_finalize_region(szone_t *szone, magazine_t *small_mag_ptr) {
3410 void *last_block, *previous_block;
3411 msize_t last_msize, previous_msize, last_index;
3412
3413 last_block = SMALL_REGION_END(small_mag_ptr->mag_last_region) - small_mag_ptr->mag_bytes_free_at_end;
3414 last_msize = SMALL_MSIZE_FOR_BYTES(small_mag_ptr->mag_bytes_free_at_end);
3415
3416 // It is possible that the block prior to the last block in the region has
3417 // been free'd, but was not coalesced with the free bytes at the end of the
3418 // block, since we treat the bytes at the end of the region as "in use" in
3419 // the meta headers. Attempt to coalesce the last block with the previous
3420 // block, so we don't violate the "no consecutive free blocks" invariant.
3421 //
3422 // FIXME: If we could calculate the previous small free size in the same
3423 // manner as tiny_previous_preceding_free, it would eliminate the
3424 // index & previous msize checks, which are a guard against reading
3425 // bogus data out of in-use or written-on-freed memory.
3426 //
3427 // FIXME: Need to investigate how much work would be required to increase
3428 // 'mag_bytes_free_at_end' when freeing the preceding block, rather
3429 // than performing this workaround.
3430 //
3431 last_index = SMALL_META_INDEX_FOR_PTR(last_block);
3432 previous_msize = SMALL_PREVIOUS_MSIZE(last_block);
3433
3434 if (last_index && (previous_msize <= last_index)) {
3435 previous_block = (void *)((uintptr_t)last_block - SMALL_BYTES_FOR_MSIZE(previous_msize));
3436 if (*SMALL_METADATA_FOR_PTR(previous_block) == (previous_msize | SMALL_IS_FREE)) {
3437 msize_t *meta_headers = SMALL_META_HEADER_FOR_PTR(last_block);
3438
3439 small_meta_header_set_middle(meta_headers, last_index);
3440 small_free_list_remove_ptr(szone, small_mag_ptr, previous_block, previous_msize);
3441 last_block = (void *)((uintptr_t)last_block - SMALL_BYTES_FOR_MSIZE(previous_msize));
3442 last_msize += previous_msize;
3443 }
3444 }
3445
3446 // splice last_block into the free list
3447 small_free_list_add_ptr(szone, small_mag_ptr, last_block, last_msize);
3448 small_mag_ptr->mag_bytes_free_at_end = 0;
3449 small_mag_ptr->mag_last_region = NULL;
3450 }
3451
3452 static int
3453 small_free_detach_region(szone_t *szone, magazine_t *small_mag_ptr, region_t r) {
3454 unsigned char *ptr = SMALL_REGION_ADDRESS(r);
3455 msize_t *meta_headers = SMALL_META_HEADER_FOR_PTR(ptr);
3456 uintptr_t start = (uintptr_t)SMALL_REGION_ADDRESS(r);
3457 uintptr_t current = start;
3458 uintptr_t limit = (uintptr_t)SMALL_REGION_END(r);
3459 int total_alloc = 0;
3460
3461 while (current < limit) {
3462 unsigned index = SMALL_META_INDEX_FOR_PTR(current);
3463 msize_t msize_and_free = meta_headers[index];
3464 boolean_t is_free = msize_and_free & SMALL_IS_FREE;
3465 msize_t msize = msize_and_free & ~ SMALL_IS_FREE;
3466
3467 if (!msize) {
3468 #if DEBUG_MALLOC
3469 malloc_printf("*** small_free_detach_region error with %p: msize=%d is_free =%d\n",
3470 (void *)current, msize, is_free);
3471 #endif
3472 break;
3473 }
3474 if (is_free) {
3475 small_free_list_remove_ptr(szone, small_mag_ptr, (void *)current, msize);
3476 } else {
3477 total_alloc++;
3478 }
3479 current += SMALL_BYTES_FOR_MSIZE(msize);
3480 }
3481 return total_alloc;
3482 }
3483
3484 static size_t
3485 small_free_reattach_region(szone_t *szone, magazine_t *small_mag_ptr, region_t r) {
3486 unsigned char *ptr = SMALL_REGION_ADDRESS(r);
3487 msize_t *meta_headers = SMALL_META_HEADER_FOR_PTR(ptr);
3488 uintptr_t start = (uintptr_t)SMALL_REGION_ADDRESS(r);
3489 uintptr_t current = start;
3490 uintptr_t limit = (uintptr_t)SMALL_REGION_END(r);
3491 size_t total_alloc = 0;
3492
3493 while (current < limit) {
3494 unsigned index = SMALL_META_INDEX_FOR_PTR(current);
3495 msize_t msize_and_free = meta_headers[index];
3496 boolean_t is_free = msize_and_free & SMALL_IS_FREE;
3497 msize_t msize = msize_and_free & ~ SMALL_IS_FREE;
3498
3499 if (!msize) {
3500 #if DEBUG_MALLOC
3501 malloc_printf("*** small_free_reattach_region error with %p: msize=%d is_free =%d\n",
3502 (void *)current, msize, is_free);
3503 #endif
3504 break;
3505 }
3506 if (is_free) {
3507 small_free_list_add_ptr(szone, small_mag_ptr, (void *)current, msize);
3508 } else {
3509 total_alloc += SMALL_BYTES_FOR_MSIZE(msize);
3510 }
3511 current += SMALL_BYTES_FOR_MSIZE(msize);
3512 }
3513 return total_alloc;
3514 }
3515
3516 static void
3517 small_free_scan_depot_madvise_free(szone_t *szone, magazine_t *depot_ptr, region_t r) {
3518 uintptr_t start = (uintptr_t)SMALL_REGION_ADDRESS(r);
3519 uintptr_t current = start;
3520 uintptr_t limit = (uintptr_t)SMALL_REGION_END(r);
3521 msize_t *meta_headers = SMALL_META_HEADER_FOR_PTR(start);
3522 boolean_t did_advise = FALSE;
3523
3524 // Scan the metadata identifying blocks which span one or more pages. Mark the pages MADV_FREE taking care to preserve free list
3525 // management data.
3526 while (current < limit) {
3527 unsigned index = SMALL_META_INDEX_FOR_PTR(current);
3528 msize_t msize_and_free = meta_headers[index];
3529 boolean_t is_free = msize_and_free & SMALL_IS_FREE;
3530 msize_t msize = msize_and_free & ~ SMALL_IS_FREE;
3531
3532 if (is_free && !msize && (current == start)) {
3533 #if DEBUG_MALLOC
3534 // first block is all free
3535 malloc_printf("*** small_free_scan_depot_madvise_free first block is all free! %p: msize=%d is_free =%d\n",
3536 (void *)current, msize, is_free);
3537 #endif
3538 uintptr_t pgLo = round_page(start + sizeof(free_list_t) + sizeof(msize_t));
3539 uintptr_t pgHi = trunc_page(start + SMALL_REGION_SIZE - sizeof(msize_t));
3540
3541 if (pgLo < pgHi) {
3542 madvise_free_range(szone, r, pgLo, pgHi);
3543 did_advise = TRUE;
3544 }
3545 break;
3546 }
3547 if (!msize) {
3548 #if DEBUG_MALLOC
3549 malloc_printf("*** small_free_scan_depot_madvise_free error with %p: msize=%d is_free =%d\n",
3550 (void *)current, msize, is_free);
3551 #endif
3552 break;
3553 }
3554 if (is_free) {
3555 uintptr_t pgLo = round_page(current + sizeof(free_list_t) + sizeof(msize_t));
3556 uintptr_t pgHi = trunc_page(current + SMALL_BYTES_FOR_MSIZE(msize) - sizeof(msize_t));
3557
3558 if (pgLo < pgHi) {
3559 madvise_free_range(szone, r, pgLo, pgHi);
3560 did_advise = TRUE;
3561 }
3562 }
3563 current += SMALL_BYTES_FOR_MSIZE(msize);
3564 }
3565
3566 if (did_advise) {
3567 /* Move the node to the tail of the Deopt's recirculation list to delay its re-use. */
3568 region_trailer_t *node = REGION_TRAILER_FOR_SMALL_REGION(r);
3569 recirc_list_extract(szone, depot_ptr, node); // excise node from list
3570 recirc_list_splice_last(szone, depot_ptr, node); // connect to magazine as last node
3571 }
3572 }
3573
3574 static void
3575 small_free_try_depot_unmap_no_lock(szone_t *szone, magazine_t *depot_ptr, region_trailer_t *node)
3576 {
3577 #warning Tune Depot headroom
3578 if (0 < node->bytes_used ||
3579 depot_ptr->recirculation_entries < (szone->num_small_magazines * 2)) {
3580 return;
3581 }
3582
3583 // disconnect first node from Depot
3584 recirc_list_extract(szone, depot_ptr, node);
3585
3586 // Iterate the region pulling its free entries off the (locked) Depot's free list
3587 region_t sparse_region = SMALL_REGION_FOR_PTR(node);
3588 int objects_in_use = small_free_detach_region(szone, depot_ptr, sparse_region);
3589
3590 if (0 == objects_in_use) {
3591 // Invalidate the hash table entry for this region with HASHRING_REGION_DEALLOCATED.
3592 // Using HASHRING_REGION_DEALLOCATED preserves the collision chain, using HASHRING_OPEN_ENTRY (0) would not.
3593 rgnhdl_t pSlot = hash_lookup_region_no_lock(szone->small_region_generation->hashed_regions,
3594 szone->small_region_generation->num_regions_allocated,
3595 szone->small_region_generation->num_regions_allocated_shift, sparse_region);
3596 *pSlot = HASHRING_REGION_DEALLOCATED;
3597 depot_ptr->num_bytes_in_magazine -= SMALL_REGION_PAYLOAD_BYTES;
3598 #if ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ >= 1))) /* GCC 4.1 and forward supports atomic builtins */
3599 __sync_fetch_and_add( &(szone->num_small_regions_dealloc), 1); // Atomically increment num_small_regions_dealloc
3600 #else
3601 #ifdef __LP64__
3602 OSAtomicIncrement64( (volatile int64_t *)&(szone->num_small_regions_dealloc) );
3603 #else
3604 OSAtomicIncrement32( (volatile int32_t *)&(szone->num_small_regions_dealloc) );
3605 #endif
3606 #endif
3607
3608 // Transfer ownership of the region back to the OS
3609 SZONE_MAGAZINE_PTR_UNLOCK(szone, depot_ptr); // Avoid denial of service to Depot while in kernel
3610 deallocate_pages(szone, sparse_region, SMALL_REGION_SIZE, 0);
3611 SZONE_MAGAZINE_PTR_LOCK(szone, depot_ptr);
3612
3613 MAGMALLOC_DEALLOCREGION((void *)szone, (void *)sparse_region); // DTrace USDT Probe
3614
3615 } else {
3616 szone_error(szone, 1, "small_free_try_depot_unmap_no_lock objects_in_use not zero:", NULL, "%d\n", objects_in_use);
3617 }
3618 }
3619
3620 static void
3621 small_free_do_recirc_to_depot(szone_t *szone, magazine_t *small_mag_ptr, mag_index_t mag_index)
3622 {
3623 // The entire magazine crossed the "emptiness threshold". Transfer a region
3624 // from this magazine to the Depot. Choose a region that itself has crossed the emptiness threshold (i.e
3625 // is at least fraction "f" empty.) Such a region will be marked "suitable" on the recirculation list.
3626 region_trailer_t *node = small_mag_ptr->firstNode;
3627
3628 while (node && !node->recirc_suitable) {
3629 node = node->next;
3630 }
3631
3632 if (NULL == node) {
3633 #if DEBUG_MALLOC
3634 malloc_printf("*** small_free_do_recirc_to_depot end of list\n");
3635 #endif
3636 return;
3637 }
3638
3639 region_t sparse_region = SMALL_REGION_FOR_PTR(node);
3640
3641 // Deal with unclaimed memory -- mag_bytes_free_at_end
3642 if (sparse_region == small_mag_ptr->mag_last_region && small_mag_ptr->mag_bytes_free_at_end) {
3643 small_finalize_region(szone, small_mag_ptr);
3644 }
3645
3646 // disconnect first node from magazine
3647 recirc_list_extract(szone, small_mag_ptr, node);
3648
3649 // Iterate the region pulling its free entries off its (locked) magazine's free list
3650 int objects_in_use = small_free_detach_region(szone, small_mag_ptr, sparse_region);
3651 magazine_t *depot_ptr = &(szone->small_magazines[DEPOT_MAGAZINE_INDEX]);
3652
3653 // hand over the region to the (locked) Depot
3654 SZONE_MAGAZINE_PTR_LOCK(szone,depot_ptr);
3655 // this will cause small_free_list_add_ptr called by small_free_reattach_region to use
3656 // the depot as its target magazine, rather than magazine formerly associated with sparse_region
3657 MAGAZINE_INDEX_FOR_SMALL_REGION(sparse_region) = DEPOT_MAGAZINE_INDEX;
3658
3659 // Iterate the region putting its free entries on Depot's free list
3660 size_t bytes_inplay = small_free_reattach_region(szone, depot_ptr, sparse_region);
3661
3662 small_mag_ptr->mag_num_bytes_in_objects -= bytes_inplay;
3663 small_mag_ptr->num_bytes_in_magazine -= SMALL_REGION_PAYLOAD_BYTES;
3664 small_mag_ptr->mag_num_objects -= objects_in_use;
3665
3666 depot_ptr->mag_num_bytes_in_objects += bytes_inplay;
3667 depot_ptr->num_bytes_in_magazine += SMALL_REGION_PAYLOAD_BYTES;
3668 depot_ptr->mag_num_objects += objects_in_use;
3669
3670 // connect to Depot as first node
3671 recirc_list_splice_first(szone, depot_ptr, node);
3672
3673 MAGMALLOC_RECIRCREGION((void *)szone, (int)mag_index, (int)BYTES_USED_FOR_SMALL_REGION(sparse_region)); // DTrace USDT Probe
3674
3675 // Mark free'd dirty pages with MADV_FREE to reduce memory pressure
3676 small_free_scan_depot_madvise_free(szone, depot_ptr, sparse_region);
3677
3678 // If the region is entirely empty vm_deallocate() it
3679 small_free_try_depot_unmap_no_lock(szone, depot_ptr, node);
3680
3681 SZONE_MAGAZINE_PTR_UNLOCK(szone,depot_ptr);
3682 }
3683
3684 static boolean_t
3685 small_get_region_from_depot(szone_t *szone, magazine_t *small_mag_ptr, mag_index_t mag_index)
3686 {
3687 magazine_t *depot_ptr = &(szone->small_magazines[DEPOT_MAGAZINE_INDEX]);
3688
3689 /* FIXME: Would Uniprocessor benefit from recirc and MADV_FREE? */
3690 if (szone->num_small_magazines == 1) // Uniprocessor, single magazine, so no recirculation necessary
3691 return 0;
3692
3693 #if DEBUG_MALLOC
3694 if (DEPOT_MAGAZINE_INDEX == mag_index) {
3695 szone_error(szone, 1, "small_get_region_from_depot called for magazine index -1", NULL, NULL);
3696 return 0;
3697 }
3698 #endif
3699
3700 SZONE_MAGAZINE_PTR_LOCK(szone,depot_ptr);
3701
3702 // Appropriate one of the Depot's regions. Prefer LIFO selection for best cache utilization.
3703 region_trailer_t *node = depot_ptr->firstNode;
3704
3705 if (NULL == node) { // Depot empty?
3706 SZONE_MAGAZINE_PTR_UNLOCK(szone,depot_ptr);
3707 return 0;
3708 }
3709
3710 // disconnect first node from Depot
3711 recirc_list_extract(szone, depot_ptr, node);
3712
3713 // Iterate the region pulling its free entries off the (locked) Depot's free list
3714 region_t sparse_region = SMALL_REGION_FOR_PTR(node);
3715 int objects_in_use = small_free_detach_region(szone, depot_ptr, sparse_region);
3716
3717 // Transfer ownership of the region
3718 MAGAZINE_INDEX_FOR_SMALL_REGION(sparse_region) = mag_index;
3719
3720 // Iterate the region putting its free entries on its new (locked) magazine's free list
3721 size_t bytes_inplay = small_free_reattach_region(szone, small_mag_ptr, sparse_region);
3722
3723 depot_ptr->mag_num_bytes_in_objects -= bytes_inplay;
3724 depot_ptr->num_bytes_in_magazine -= SMALL_REGION_PAYLOAD_BYTES;
3725 depot_ptr->mag_num_objects -= objects_in_use;
3726
3727 small_mag_ptr->mag_num_bytes_in_objects += bytes_inplay;
3728 small_mag_ptr->num_bytes_in_magazine += SMALL_REGION_PAYLOAD_BYTES;
3729 small_mag_ptr->mag_num_objects += objects_in_use;
3730
3731 // connect to magazine as first node (it's maximally sparse at this moment)
3732 recirc_list_splice_first(szone, small_mag_ptr, node);
3733
3734 SZONE_MAGAZINE_PTR_UNLOCK(szone,depot_ptr);
3735
3736 MAGMALLOC_DEPOTREGION((void *)szone, (int)mag_index, (int)BYTES_USED_FOR_SMALL_REGION(sparse_region)); // DTrace USDT Probe
3737
3738 if (-1 == madvise((void *)sparse_region, SMALL_REGION_PAYLOAD_BYTES, MADV_FREE_REUSE)) {
3739 /* -1 return: VM map entry change makes this unfit for reuse. Something evil lurks. */
3740 #if DEBUG_MALLOC
3741 szone_error(szone, 1, "small_get_region_from_depot madvise(..., MADV_FREE_REUSE) failed", sparse_region, NULL);
3742 #endif
3743 return 0;
3744 }
3745
3746 return 1;
3747 }
3748
3749 #warning Tune K and f!
3750 #define K 1.5 // headroom measured in number of 8Mb regions
3751 #define DENSITY_THRESHOLD(a) \
3752 ((a) - ((a) >> 2)) // "Emptiness" f = 0.25, so "Density" is (1 - f)*a. Generally: ((a) - ((a) >> -log2(f)))
3753
3754 static INLINE void
3755 small_free_no_lock(szone_t *szone, magazine_t *small_mag_ptr, mag_index_t mag_index, region_t region, void *ptr, msize_t msize)
3756 {
3757 msize_t *meta_headers = SMALL_META_HEADER_FOR_PTR(ptr);
3758 unsigned index = SMALL_META_INDEX_FOR_PTR(ptr);
3759 void *original_ptr = ptr;
3760 size_t original_size = SMALL_BYTES_FOR_MSIZE(msize);
3761 unsigned char *next_block = ((unsigned char *)ptr + original_size);
3762 msize_t next_index = index + msize;
3763 msize_t previous_msize, next_msize;
3764 void *previous;
3765 boolean_t did_prepend = FALSE;
3766 boolean_t did_append = FALSE;
3767
3768 #if DEBUG_MALLOC
3769 if (LOG(szone,ptr)) {
3770 malloc_printf("in small_free_no_lock(), ptr=%p, msize=%d\n", ptr, msize);
3771 }
3772 if (! msize) {
3773 szone_error(szone, 1, "trying to free small block that is too small", ptr,
3774 "in small_free_no_lock(), ptr=%p, msize=%d\n", ptr, msize);
3775 }
3776 #endif
3777
3778 // We try to coalesce this block with the preceeding one
3779 if (index && (SMALL_PREVIOUS_MSIZE(ptr) <= index)) {
3780 previous_msize = SMALL_PREVIOUS_MSIZE(ptr);
3781 if (meta_headers[index - previous_msize] == (previous_msize | SMALL_IS_FREE)) {
3782 previous = (void *)((uintptr_t)ptr - SMALL_BYTES_FOR_MSIZE(previous_msize));
3783 // previous is really to be coalesced
3784 did_prepend = TRUE;
3785 #if DEBUG_MALLOC
3786 if (LOG(szone, ptr) || LOG(szone,previous)) {
3787 malloc_printf("in small_free_no_lock(), coalesced backwards for %p previous=%p\n", ptr, previous);
3788 }
3789 #endif
3790 small_free_list_remove_ptr(szone, small_mag_ptr, previous, previous_msize);
3791 small_meta_header_set_middle(meta_headers, index);
3792 ptr = previous;
3793 msize += previous_msize;
3794 index -= previous_msize;
3795 }
3796 }
3797 // We try to coalesce with the next block
3798 if ((next_block < SMALL_REGION_END(region)) && (meta_headers[next_index] & SMALL_IS_FREE)) {
3799 // next block is free, we coalesce
3800 did_append = TRUE;
3801 next_msize = meta_headers[next_index] & ~ SMALL_IS_FREE;
3802 #if DEBUG_MALLOC
3803 if (LOG(szone,ptr))
3804 malloc_printf("In small_free_no_lock(), for ptr=%p, msize=%d coalesced next block=%p next_msize=%d\n",
3805 ptr, msize, next_block, next_msize);
3806 #endif
3807 small_free_list_remove_ptr(szone, small_mag_ptr, next_block, next_msize);
3808 small_meta_header_set_middle(meta_headers, next_index);
3809 msize += next_msize;
3810 }
3811 if (szone->debug_flags & SCALABLE_MALLOC_DO_SCRIBBLE) {
3812 if (!msize) {
3813 szone_error(szone, 1, "incorrect size information - block header was damaged", ptr, NULL);
3814 } else {
3815 memset(ptr, 0x55, SMALL_BYTES_FOR_MSIZE(msize));
3816 }
3817 }
3818 small_free_list_add_ptr(szone, small_mag_ptr, ptr, msize);
3819 small_mag_ptr->mag_num_objects--;
3820 // we use original_size and not msize to avoid double counting the coalesced blocks
3821 small_mag_ptr->mag_num_bytes_in_objects -= original_size;
3822
3823 // Update this region's bytes in use count
3824 region_trailer_t *node = REGION_TRAILER_FOR_SMALL_REGION(region);
3825 size_t bytes_used = node->bytes_used - original_size;
3826 node->bytes_used = bytes_used;
3827
3828 /* FIXME: Would Uniprocessor benefit from recirc and MADV_FREE? */
3829 if (szone->num_small_magazines == 1) { // Uniprocessor, single magazine, so no recirculation necessary
3830 /* NOTHING */
3831 } else if (DEPOT_MAGAZINE_INDEX != mag_index) {
3832 // Emptiness discriminant
3833 if (bytes_used < DENSITY_THRESHOLD(SMALL_REGION_PAYLOAD_BYTES)) {
3834 /* Region has crossed threshold from density to sparsity. Mark it "suitable" on the
3835 recirculation candidates list. */
3836 node->recirc_suitable = TRUE;
3837 } else {
3838 /* After this free, we've found the region is still dense, so it must have been even more so before
3839 the free. That implies the region is already correctly marked. Do nothing. */
3840 }
3841
3842 // Has the entire magazine crossed the "emptiness threshold"? If so, transfer a region
3843 // from this magazine to the Depot. Choose a region that itself has crossed the emptiness threshold (i.e
3844 // is at least fraction "f" empty.) Such a region will be marked "suitable" on the recirculation list.
3845
3846 size_t a = small_mag_ptr->num_bytes_in_magazine; // Total bytes allocated to this magazine
3847 size_t u = small_mag_ptr->mag_num_bytes_in_objects; // In use (malloc'd) from this magaqzine
3848
3849 if (a - u > ((3 * SMALL_REGION_PAYLOAD_BYTES) / 2) && u < DENSITY_THRESHOLD(a))
3850 small_free_do_recirc_to_depot(szone, small_mag_ptr, mag_index);
3851
3852 } else {
3853 // Freed to Depot. N.B. Lock on small_magazines[DEPOT_MAGAZINE_INDEX] is already held
3854 uintptr_t safe_ptr = (uintptr_t)ptr + sizeof(free_list_t) + sizeof(msize_t);
3855 uintptr_t round_safe = round_page(safe_ptr);
3856
3857 uintptr_t safe_extent = (uintptr_t)ptr + SMALL_BYTES_FOR_MSIZE(msize) - sizeof(msize_t);
3858 uintptr_t trunc_extent = trunc_page(safe_extent);
3859
3860 // The newly freed block may complete a span of bytes that cover a page. Mark it with MADV_FREE.
3861 if (round_safe < trunc_extent) { // Safe area covers a page (perhaps many)
3862 if (did_prepend & did_append) { // Coalesced preceding with original_ptr *and* with following
3863 uintptr_t trunc_safe_prev = trunc_page((uintptr_t)original_ptr - sizeof(msize_t));
3864 uintptr_t rnd_safe_follow =
3865 round_page((uintptr_t)original_ptr + original_size + sizeof(free_list_t) + sizeof(msize_t));
3866
3867 madvise_free_range(szone, region, MAX(round_safe, trunc_safe_prev), MIN(rnd_safe_follow, trunc_extent));
3868 } else if (did_prepend) { // Coalesced preceding with original_ptr
3869 uintptr_t trunc_safe_prev = trunc_page((uintptr_t)original_ptr - sizeof(msize_t));
3870
3871 madvise_free_range(szone, region, MAX(round_safe, trunc_safe_prev), trunc_extent);
3872 } else if (did_append) { // Coalesced original_ptr with following
3873 uintptr_t rnd_safe_follow =
3874 round_page((uintptr_t)original_ptr + original_size + sizeof(free_list_t) + sizeof(msize_t));
3875
3876 madvise_free_range(szone, region, round_safe, MIN(rnd_safe_follow, trunc_extent));
3877 } else // Isolated free
3878 madvise_free_range(szone, region, round_safe, trunc_extent);
3879 }
3880
3881 if (0 < bytes_used) {
3882 /* Depot'd region is still live. Leave it in place on the Depot's recirculation list
3883 so as to avoid thrashing between the Depot's free list and a magazines's free list
3884 with detach_region/reattach_region */
3885 } else {
3886 /* Depot'd region is just now empty. Consider return to OS. */
3887 region_trailer_t *node = REGION_TRAILER_FOR_SMALL_REGION(region);
3888 magazine_t *depot_ptr = &(szone->small_magazines[DEPOT_MAGAZINE_INDEX]);
3889 small_free_try_depot_unmap_no_lock(szone, depot_ptr, node);
3890 }
3891 }
3892 }
3893
3894 // Allocates from the last region or a freshly allocated region
3895 static void *
3896 small_malloc_from_region_no_lock(szone_t *szone, magazine_t *small_mag_ptr, mag_index_t mag_index, msize_t msize)
3897 {
3898 void *ptr, *aligned_address;
3899
3900 // Before anything we transform the mag_bytes_free_at_end - if any - to a regular free block
3901 /* FIXME: last_block needs to be coalesced with previous entry if free, <rdar://5462322> */
3902 if (small_mag_ptr->mag_bytes_free_at_end)
3903 small_finalize_region(szone, small_mag_ptr);
3904
3905 // time to create a new region
3906 aligned_address = allocate_pages(szone, SMALL_REGION_SIZE, SMALL_BLOCKS_ALIGN, 0, VM_MEMORY_MALLOC_SMALL);
3907 if (!aligned_address)
3908 return NULL;
3909
3910 MAGMALLOC_ALLOCREGION((void *)szone, (int)mag_index); // DTrace USDT Probe
3911
3912 // Here find the only place in smallville that (infrequently) takes the small_regions_lock.
3913 // Only one thread at a time should be permitted to assess the density of the hash
3914 // ring and adjust if needed.
3915 // Only one thread at a time should be permitted to insert its new region on
3916 // the hash ring.
3917 // It is safe for all other threads to read the hash ring (hashed_regions) and
3918 // the associated sizes (num_regions_allocated and num_small_regions).
3919
3920 LOCK(szone->small_regions_lock);
3921 // Check to see if the hash ring of small regions needs to grow. Try to
3922 // avoid the hash ring becoming too dense.
3923 if (szone->small_region_generation->num_regions_allocated < (2 * szone->num_small_regions)) {
3924 region_t *new_regions;
3925 size_t new_size;
3926 size_t new_shift = szone->small_region_generation->num_regions_allocated_shift; // In/Out parameter
3927 new_regions = hash_regions_grow_no_lock(szone, szone->small_region_generation->hashed_regions,
3928 szone->small_region_generation->num_regions_allocated,
3929 &new_shift,
3930 &new_size);
3931 // Do not deallocate the current hashed_regions allocation since someone
3932 // may be iterating it. Instead, just leak it.
3933
3934 // Prepare to advance to the "next generation" of the hash ring.
3935 szone->small_region_generation->nextgen->hashed_regions = new_regions;
3936 szone->small_region_generation->nextgen->num_regions_allocated = new_size;
3937 szone->small_region_generation->nextgen->num_regions_allocated_shift = new_shift;
3938
3939 // Throw the switch to atomically advance to the next generation.
3940 szone->small_region_generation = szone->small_region_generation->nextgen;
3941 // Ensure everyone sees the advance.
3942 #if ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ >= 1))) /* GCC 4.1 and forward supports atomic builtins */
3943 __sync_synchronize();
3944 #else
3945 OSMemoryBarrier();
3946 #endif
3947 }
3948 // Tag the region at "aligned_address" as belonging to us,
3949 // and so put it under the protection of the magazine lock we are holding.
3950 // Do this before advertising "aligned_address" on the hash ring(!)
3951 MAGAZINE_INDEX_FOR_SMALL_REGION(aligned_address) = mag_index;
3952
3953 // Insert the new region into the hash ring, and update malloc statistics
3954 hash_region_insert_no_lock(szone->small_region_generation->hashed_regions,
3955 szone->small_region_generation->num_regions_allocated,
3956 szone->small_region_generation->num_regions_allocated_shift,
3957 aligned_address);
3958
3959 szone->num_small_regions++;
3960
3961 UNLOCK(szone->small_regions_lock);
3962
3963 small_mag_ptr->mag_last_region = aligned_address;
3964 BYTES_USED_FOR_SMALL_REGION(aligned_address) = SMALL_BYTES_FOR_MSIZE(msize);
3965 ptr = aligned_address;
3966 small_meta_header_set_in_use(SMALL_META_HEADER_FOR_PTR(ptr), 0, msize);
3967 small_mag_ptr->mag_num_objects++;
3968 small_mag_ptr->mag_num_bytes_in_objects += SMALL_BYTES_FOR_MSIZE(msize);
3969 small_mag_ptr->num_bytes_in_magazine += SMALL_REGION_PAYLOAD_BYTES;
3970
3971 // add a big free block
3972 small_meta_header_set_in_use(SMALL_META_HEADER_FOR_PTR(ptr) , msize, NUM_SMALL_BLOCKS - msize);
3973 small_mag_ptr->mag_bytes_free_at_end = SMALL_BYTES_FOR_MSIZE(NUM_SMALL_BLOCKS - msize);
3974
3975 // connect to magazine as first node (it's maximally sparse at this moment)
3976 recirc_list_splice_first(szone, small_mag_ptr, REGION_TRAILER_FOR_SMALL_REGION(aligned_address));
3977
3978 return ptr;
3979 }
3980
3981 static INLINE boolean_t
3982 small_try_realloc_in_place(szone_t *szone, void *ptr, size_t old_size, size_t new_size)
3983 {
3984 // returns 1 on success
3985 msize_t *meta_headers = SMALL_META_HEADER_FOR_PTR(ptr);
3986 unsigned index;
3987 msize_t old_msize, new_msize;
3988 unsigned next_index;
3989 void *next_block;
3990 msize_t next_msize_and_free;
3991 boolean_t is_free;
3992 msize_t next_msize, leftover_msize;
3993 void *leftover;
3994
3995 index = SMALL_META_INDEX_FOR_PTR(ptr);
3996 old_msize = SMALL_MSIZE_FOR_BYTES(old_size);
3997 new_msize = SMALL_MSIZE_FOR_BYTES(new_size + SMALL_QUANTUM - 1);
3998 next_index = index + old_msize;
3999
4000 if (next_index >= NUM_SMALL_BLOCKS) {
4001 return 0;
4002 }
4003 next_block = (char *)ptr + old_size;
4004
4005 #if DEBUG_MALLOC
4006 if ((uintptr_t)next_block & (SMALL_QUANTUM - 1)) {
4007 szone_error(szone, 1, "internal invariant broken in realloc(next_block)", next_block, NULL);
4008 }
4009 if (meta_headers[index] != old_msize)
4010 malloc_printf("*** small_try_realloc_in_place incorrect old %d %d\n",
4011 meta_headers[index], old_msize);
4012 #endif
4013
4014 magazine_t *small_mag_ptr = mag_lock_zine_for_region_trailer(szone, szone->small_magazines,
4015 REGION_TRAILER_FOR_SMALL_REGION(SMALL_REGION_FOR_PTR(ptr)),
4016 MAGAZINE_INDEX_FOR_SMALL_REGION(SMALL_REGION_FOR_PTR(ptr)));
4017
4018 /*
4019 * Look for a free block immediately afterwards. If it's large enough, we can consume (part of)
4020 * it.
4021 */
4022 next_msize_and_free = meta_headers[next_index];
4023 is_free = next_msize_and_free & SMALL_IS_FREE;
4024 if (!is_free) {
4025 SZONE_MAGAZINE_PTR_UNLOCK(szone, small_mag_ptr);
4026 return 0; // next_block is in use;
4027 }
4028 next_msize = next_msize_and_free & ~ SMALL_IS_FREE;
4029 if (old_msize + next_msize < new_msize) {
4030 SZONE_MAGAZINE_PTR_UNLOCK(szone, small_mag_ptr);
4031 return 0; // even with next block, not enough
4032 }
4033 /*
4034 * The following block is big enough; pull it from its freelist and chop off enough to satisfy
4035 * our needs.
4036 */
4037 small_free_list_remove_ptr(szone, small_mag_ptr, next_block, next_msize);
4038 small_meta_header_set_middle(meta_headers, next_index);
4039 leftover_msize = old_msize + next_msize - new_msize;
4040 if (leftover_msize) {
4041 /* there's some left, so put the remainder back */
4042 leftover = (unsigned char *)ptr + SMALL_BYTES_FOR_MSIZE(new_msize);
4043
4044 small_free_list_add_ptr(szone, small_mag_ptr, leftover, leftover_msize);
4045 }
4046 #if DEBUG_MALLOC
4047 if (SMALL_BYTES_FOR_MSIZE(new_msize) > szone->large_threshold) {
4048 malloc_printf("*** realloc in place for %p exceeded msize=%d\n", new_msize);
4049 }
4050 #endif
4051 small_meta_header_set_in_use(meta_headers, index, new_msize);
4052 #if DEBUG_MALLOC
4053 if (LOG(szone,ptr)) {
4054 malloc_printf("in szone_realloc(), ptr=%p, msize=%d\n", ptr, *SMALL_METADATA_FOR_PTR(ptr));
4055 }
4056 #endif
4057 small_mag_ptr->mag_num_bytes_in_objects += SMALL_BYTES_FOR_MSIZE(new_msize - old_msize);
4058
4059 // Update this region's bytes in use count
4060 region_trailer_t *node = REGION_TRAILER_FOR_SMALL_REGION(SMALL_REGION_FOR_PTR(ptr));
4061 size_t bytes_used = node->bytes_used + SMALL_BYTES_FOR_MSIZE(new_msize - old_msize);
4062 node->bytes_used = bytes_used;
4063
4064 // Emptiness discriminant
4065 if (bytes_used < DENSITY_THRESHOLD(SMALL_REGION_PAYLOAD_BYTES)) {
4066 /* After this reallocation the region is still sparse, so it must have been even more so before
4067 the reallocation. That implies the region is already correctly marked. Do nothing. */
4068 } else {
4069 /* Region has crossed threshold from sparsity to density. Mark it not "suitable" on the
4070 recirculation candidates list. */
4071 node->recirc_suitable = FALSE;
4072 }
4073
4074 SZONE_MAGAZINE_PTR_UNLOCK(szone, small_mag_ptr);
4075 CHECK(szone, __PRETTY_FUNCTION__);
4076 return 1;
4077 }
4078
4079 static boolean_t
4080 small_check_region(szone_t *szone, region_t region)
4081 {
4082 unsigned char *ptr = SMALL_REGION_ADDRESS(region);
4083 msize_t *meta_headers = SMALL_META_HEADER_FOR_PTR(ptr);
4084 unsigned char *region_end = SMALL_REGION_END(region);
4085 msize_t prev_free = 0;
4086 unsigned index;
4087 msize_t msize_and_free;
4088 msize_t msize;
4089 free_list_t *free_head;
4090 void *previous, *next;
4091 msize_t *follower;
4092 mag_index_t mag_index = MAGAZINE_INDEX_FOR_SMALL_REGION(SMALL_REGION_FOR_PTR(ptr));
4093 magazine_t *small_mag_ptr = &(szone->small_magazines[mag_index]);
4094
4095 // Assumes locked
4096 CHECK_MAGAZINE_PTR_LOCKED(szone, small_mag_ptr, __PRETTY_FUNCTION__);
4097
4098 if (region == small_mag_ptr->mag_last_region)
4099 region_end -= small_mag_ptr->mag_bytes_free_at_end;
4100
4101 while (ptr < region_end) {
4102 index = SMALL_META_INDEX_FOR_PTR(ptr);
4103 msize_and_free = meta_headers[index];
4104 if (!(msize_and_free & SMALL_IS_FREE)) {
4105 // block is in use
4106 msize = msize_and_free;
4107 if (!msize) {
4108 malloc_printf("*** invariant broken: null msize ptr=%p num_small_regions=%d end=%p\n",
4109 ptr, szone->num_small_regions, region_end);
4110 return 0;
4111 }
4112 if (SMALL_BYTES_FOR_MSIZE(msize) > szone->large_threshold) {
4113 malloc_printf("*** invariant broken for %p this small msize=%d - size is too large\n",
4114 ptr, msize_and_free);
4115 return 0;
4116 }
4117 ptr += SMALL_BYTES_FOR_MSIZE(msize);
4118 prev_free = 0;
4119 } else {
4120 // free pointer
4121 msize = msize_and_free & ~ SMALL_IS_FREE;
4122 free_head = (free_list_t *)ptr;
4123 follower = (msize_t *)FOLLOWING_SMALL_PTR(ptr, msize);
4124 if (!msize) {
4125 malloc_printf("*** invariant broken for free block %p this msize=%d\n", ptr, msize);
4126 return 0;
4127 }
4128 if (prev_free) {
4129 malloc_printf("*** invariant broken for %p (2 free in a row)\n", ptr);
4130 return 0;
4131 }
4132 previous = free_list_unchecksum_ptr(szone, &free_head->previous);
4133 next = free_list_unchecksum_ptr(szone, &free_head->next);
4134 if (previous && !SMALL_PTR_IS_FREE(previous)) {
4135 malloc_printf("*** invariant broken for %p (previous %p is not a free pointer)\n",
4136 ptr, free_head->previous);
4137 return 0;
4138 }
4139 if (next && !SMALL_PTR_IS_FREE(next)) {
4140 malloc_printf("*** invariant broken for %p (next is not a free pointer)\n", ptr);
4141 return 0;
4142 }
4143 if (SMALL_PREVIOUS_MSIZE(follower) != msize) {
4144 malloc_printf("*** invariant broken for small free %p followed by %p in region [%p-%p] "
4145 "(end marker incorrect) should be %d; in fact %d\n",
4146 ptr, follower, SMALL_REGION_ADDRESS(region), region_end, msize, SMALL_PREVIOUS_MSIZE(follower));
4147 return 0;
4148 }
4149 ptr = (unsigned char *)follower;
4150 prev_free = SMALL_IS_FREE;
4151 }
4152 }
4153 return 1;
4154 }
4155
4156 static kern_return_t
4157 small_in_use_enumerator(task_t task, void *context, unsigned type_mask, szone_t *szone,
4158 memory_reader_t reader, vm_range_recorder_t recorder)
4159 {
4160 size_t num_regions;
4161 size_t index;
4162 region_t *regions;
4163 vm_range_t buffer[MAX_RECORDER_BUFFER];
4164 unsigned count = 0;
4165 kern_return_t err;
4166 region_t region;
4167 vm_range_t range;
4168 vm_range_t admin_range;
4169 vm_range_t ptr_range;
4170 unsigned char *mapped_region;
4171 msize_t *block_header;
4172 unsigned block_index;
4173 unsigned block_limit;
4174 msize_t msize_and_free;
4175 msize_t msize;
4176 vm_address_t mag_last_free_ptr = 0;
4177 msize_t mag_last_free_msize = 0;
4178
4179 region_hash_generation_t *srg_ptr;
4180 err = reader(task, (vm_address_t)szone->small_region_generation, sizeof(region_hash_generation_t), (void **)&srg_ptr);
4181 if (err) return err;
4182
4183 num_regions = srg_ptr->num_regions_allocated;
4184 err = reader(task, (vm_address_t)srg_ptr->hashed_regions, sizeof(region_t) * num_regions, (void **)&regions);
4185 if (err) return err;
4186
4187 for (index = 0; index < num_regions; ++index) {
4188 region = regions[index];
4189 if (HASHRING_OPEN_ENTRY != region && HASHRING_REGION_DEALLOCATED != region) {
4190 range.address = (vm_address_t)SMALL_REGION_ADDRESS(region);
4191 range.size = SMALL_REGION_SIZE;
4192 if (type_mask & MALLOC_ADMIN_REGION_RANGE_TYPE) {
4193 admin_range.address = range.address + SMALL_METADATA_START;
4194 admin_range.size = SMALL_METADATA_SIZE;
4195 recorder(task, context, MALLOC_ADMIN_REGION_RANGE_TYPE, &admin_range, 1);
4196 }
4197 if (type_mask & (MALLOC_PTR_REGION_RANGE_TYPE | MALLOC_ADMIN_REGION_RANGE_TYPE)) {
4198 ptr_range.address = range.address;
4199 ptr_range.size = NUM_SMALL_BLOCKS * SMALL_QUANTUM;
4200 recorder(task, context, MALLOC_PTR_REGION_RANGE_TYPE, &ptr_range, 1);
4201 }
4202 if (type_mask & MALLOC_PTR_IN_USE_RANGE_TYPE) {
4203 err = reader(task, range.address, range.size, (void **)&mapped_region);
4204 if (err)
4205 return err;
4206
4207 mag_index_t mag_index = MAGAZINE_INDEX_FOR_SMALL_REGION(mapped_region);
4208 magazine_t *small_mag_ptr;
4209 err = reader(task, (vm_address_t)&(szone->small_magazines[mag_index]), sizeof(magazine_t),
4210 (void **)&small_mag_ptr);
4211 if (err) return err;
4212
4213 void *mag_last_free = small_mag_ptr->mag_last_free;
4214 if (mag_last_free) {
4215 mag_last_free_ptr = (uintptr_t) mag_last_free & ~(SMALL_QUANTUM - 1);
4216 mag_last_free_msize = (uintptr_t) mag_last_free & (SMALL_QUANTUM - 1);
4217 }
4218
4219 block_header = (msize_t *)(mapped_region + SMALL_METADATA_START + sizeof(region_trailer_t));
4220 block_index = 0;
4221 block_limit = NUM_SMALL_BLOCKS;
4222 if (region == small_mag_ptr->mag_last_region)
4223 block_limit -= SMALL_MSIZE_FOR_BYTES(small_mag_ptr->mag_bytes_free_at_end);
4224 while (block_index < block_limit) {
4225 msize_and_free = block_header[block_index];
4226 msize = msize_and_free & ~ SMALL_IS_FREE;
4227 if (! (msize_and_free & SMALL_IS_FREE) &&
4228 range.address + SMALL_BYTES_FOR_MSIZE(block_index) != mag_last_free_ptr) {
4229 // Block in use
4230 buffer[count].address = range.address + SMALL_BYTES_FOR_MSIZE(block_index);
4231 buffer[count].size = SMALL_BYTES_FOR_MSIZE(msize);
4232 count++;
4233 if (count >= MAX_RECORDER_BUFFER) {
4234 recorder(task, context, MALLOC_PTR_IN_USE_RANGE_TYPE, buffer, count);
4235 count = 0;
4236 }
4237 }
4238 block_index += msize;
4239 }
4240 if (count) {
4241 recorder(task, context, MALLOC_PTR_IN_USE_RANGE_TYPE, buffer, count);
4242 count = 0;
4243 }
4244 }
4245 }
4246 }
4247 return 0;
4248 }
4249
4250 static void *
4251 small_malloc_from_free_list(szone_t *szone, magazine_t *small_mag_ptr, mag_index_t mag_index, msize_t msize)
4252 {
4253 free_list_t *ptr;
4254 msize_t this_msize;
4255 grain_t slot = (msize <= szone->num_small_slots) ? msize - 1 : szone->num_small_slots - 1;
4256 free_list_t **free_list = small_mag_ptr->mag_free_list;
4257 free_list_t **the_slot = free_list + slot;
4258 free_list_t *next;
4259 free_list_t **limit;
4260 unsigned bitmap;
4261 msize_t leftover_msize;
4262 free_list_t *leftover_ptr;
4263
4264 // Assumes we've locked the region
4265 CHECK_MAGAZINE_PTR_LOCKED(szone, small_mag_ptr, __PRETTY_FUNCTION__);
4266
4267 // Look for an exact match by checking the freelist for this msize.
4268 //
4269 ptr = *the_slot;
4270 if (ptr) {
4271 next = free_list_unchecksum_ptr(szone, &ptr->next);
4272 if (next) {
4273 next->previous = ptr->previous;
4274 } else {
4275 BITMAPN_CLR(small_mag_ptr->mag_bitmap, slot);
4276 }
4277 *the_slot = next;
4278 this_msize = msize;
4279 goto return_small_alloc;
4280 }
4281
4282 // Mask off the bits representing slots holding free blocks smaller than
4283 // the size we need. If there are no larger free blocks, try allocating
4284 // from the free space at the end of the small region.
4285 if (szone->is_largemem) {
4286 // BITMAPN_CTZ implementation
4287 unsigned idx = slot >> 5;
4288 bitmap = 0;
4289 unsigned mask = ~ ((1 << (slot & 31)) - 1);
4290 for ( ; idx < SMALL_BITMAP_WORDS; ++idx ) {
4291 bitmap = small_mag_ptr->mag_bitmap[idx] & mask;
4292 if (bitmap != 0)
4293 break;
4294 mask = ~0U;
4295 }
4296 // Check for fallthrough: No bits set in bitmap
4297 if ((bitmap == 0) && (idx == SMALL_BITMAP_WORDS))
4298 goto try_small_from_end;
4299
4300 // Start looking at the first set bit, plus 32 bits for every word of
4301 // zeroes or entries that were too small.
4302 slot = BITMAP32_CTZ((&bitmap)) + (idx * 32);
4303 } else {
4304 bitmap = small_mag_ptr->mag_bitmap[0] & ~ ((1 << slot) - 1);
4305 if (!bitmap)
4306 goto try_small_from_end;
4307
4308 slot = BITMAP32_CTZ((&bitmap));
4309 }
4310 // FIXME: Explain use of - 1 here, last slot has special meaning
4311 limit = free_list + szone->num_small_slots - 1;
4312 free_list += slot;
4313
4314 if (free_list < limit) {
4315 ptr = *free_list;
4316 if (ptr) {
4317
4318 next = free_list_unchecksum_ptr(szone, &ptr->next);
4319 *free_list = next;
4320 if (next) {
4321 next->previous = ptr->previous;
4322 } else {
4323 BITMAPN_CLR(small_mag_ptr->mag_bitmap, slot);
4324 }
4325 this_msize = SMALL_PTR_SIZE(ptr);
4326 goto add_leftover_and_proceed;
4327 }
4328 #if DEBUG_MALLOC
4329 malloc_printf("in small_malloc_from_free_list(), mag_bitmap out of sync, slot=%d\n",slot);
4330 #endif
4331 }
4332
4333 // We are now looking at the last slot, which contains blocks equal to, or
4334 // due to coalescing of free blocks, larger than (num_small_slots - 1) * (small quantum size).
4335 // If the last freelist is not empty, and the head contains a block that is
4336 // larger than our request, then the remainder is put back on the free list.
4337 //
4338 ptr = *limit;
4339 if (ptr) {
4340 this_msize = SMALL_PTR_SIZE(ptr);
4341 next = free_list_unchecksum_ptr(szone, &ptr->next);
4342 if (this_msize - msize >= szone->num_small_slots) {
4343 // the leftover will go back to the free list, so we optimize by
4344 // modifying the free list rather than a pop and push of the head
4345 leftover_msize = this_msize - msize;
4346 leftover_ptr = (free_list_t *)((unsigned char *)ptr + SMALL_BYTES_FOR_MSIZE(msize));
4347 *limit = leftover_ptr;
4348 if (next) {
4349 next->previous.u = free_list_checksum_ptr(szone, leftover_ptr);
4350 }
4351 leftover_ptr->previous = ptr->previous;
4352 leftover_ptr->next = ptr->next;
4353 small_meta_header_set_is_free(SMALL_META_HEADER_FOR_PTR(leftover_ptr),
4354 SMALL_META_INDEX_FOR_PTR(leftover_ptr), leftover_msize);
4355 // Store msize at the end of the block denoted by "leftover_ptr" (i.e. at a negative offset from follower)
4356 SMALL_PREVIOUS_MSIZE(FOLLOWING_SMALL_PTR(leftover_ptr, leftover_msize)) = leftover_msize; // Access is safe
4357 #if DEBUG_MALLOC
4358 if (LOG(szone,ptr)) {
4359 malloc_printf("in small_malloc_from_free_list(), last slot ptr=%p, msize=%d this_msize=%d\n", ptr, msize, this_msize);
4360 }
4361 #endif
4362 this_msize = msize;
4363 goto return_small_alloc;
4364 }
4365 if (next) {
4366 next->previous = ptr->previous;
4367 }
4368 *limit = next;
4369 goto add_leftover_and_proceed;
4370 }
4371
4372 try_small_from_end:
4373 // Let's see if we can use small_mag_ptr->mag_bytes_free_at_end
4374 if (small_mag_ptr->mag_bytes_free_at_end >= SMALL_BYTES_FOR_MSIZE(msize)) {
4375 ptr = (free_list_t *)(SMALL_REGION_END(small_mag_ptr->mag_last_region) -
4376 small_mag_ptr->mag_bytes_free_at_end);
4377 small_mag_ptr->mag_bytes_free_at_end -= SMALL_BYTES_FOR_MSIZE(msize);
4378 if (small_mag_ptr->mag_bytes_free_at_end) {
4379 // let's mark this block as in use to serve as boundary
4380 small_meta_header_set_in_use(SMALL_META_HEADER_FOR_PTR(ptr),
4381 SMALL_META_INDEX_FOR_PTR((unsigned char *)ptr + SMALL_BYTES_FOR_MSIZE(msize)),
4382 SMALL_MSIZE_FOR_BYTES(small_mag_ptr->mag_bytes_free_at_end));
4383 }
4384 this_msize = msize;
4385 goto return_small_alloc;
4386 }
4387 return NULL;
4388
4389 add_leftover_and_proceed:
4390 if (this_msize > msize) {
4391 leftover_msize = this_msize - msize;
4392 leftover_ptr = (free_list_t *)((unsigned char *)ptr + SMALL_BYTES_FOR_MSIZE(msize));
4393 #if DEBUG_MALLOC
4394 if (LOG(szone,ptr)) {
4395 malloc_printf("in small_malloc_from_free_list(), adding leftover ptr=%p, this_msize=%d\n", ptr, this_msize);
4396 }
4397 #endif
4398 small_free_list_add_ptr(szone, small_mag_ptr, leftover_ptr, leftover_msize);
4399 this_msize = msize;
4400 }
4401
4402 return_small_alloc:
4403 small_mag_ptr->mag_num_objects++;
4404 small_mag_ptr->mag_num_bytes_in_objects += SMALL_BYTES_FOR_MSIZE(this_msize);
4405
4406 // Update this region's bytes in use count
4407 region_trailer_t *node = REGION_TRAILER_FOR_SMALL_REGION(SMALL_REGION_FOR_PTR(ptr));
4408 size_t bytes_used = node->bytes_used + SMALL_BYTES_FOR_MSIZE(this_msize);
4409 node->bytes_used = bytes_used;
4410
4411 // Emptiness discriminant
4412 if (bytes_used < DENSITY_THRESHOLD(SMALL_REGION_PAYLOAD_BYTES)) {
4413 /* After this allocation the region is still sparse, so it must have been even more so before
4414 the allocation. That implies the region is already correctly marked. Do nothing. */
4415 } else {
4416 /* Region has crossed threshold from sparsity to density. Mark in not "suitable" on the
4417 recirculation candidates list. */
4418 node->recirc_suitable = FALSE;
4419 }
4420 #if DEBUG_MALLOC
4421 if (LOG(szone,ptr)) {
4422 malloc_printf("in small_malloc_from_free_list(), ptr=%p, this_msize=%d, msize=%d\n", ptr, this_msize, msize);
4423 }
4424 #endif
4425 small_meta_header_set_in_use(SMALL_META_HEADER_FOR_PTR(ptr), SMALL_META_INDEX_FOR_PTR(ptr), this_msize);
4426 return ptr;
4427 }
4428 #undef DENSITY_THRESHOLD
4429 #undef K
4430
4431 static INLINE void *
4432 small_malloc_should_clear(szone_t *szone, msize_t msize, boolean_t cleared_requested)
4433 {
4434 void *ptr;
4435 mag_index_t mag_index = mag_get_thread_index(szone);
4436 magazine_t *small_mag_ptr = &(szone->small_magazines[mag_index]);
4437
4438 SZONE_MAGAZINE_PTR_LOCK(szone, small_mag_ptr);
4439
4440 #if SMALL_CACHE
4441 ptr = (void *)small_mag_ptr->mag_last_free;
4442
4443 if ((((uintptr_t)ptr) & (SMALL_QUANTUM - 1)) == msize) {
4444 // we have a winner
4445 small_mag_ptr->mag_last_free = NULL;
4446 SZONE_MAGAZINE_PTR_UNLOCK(szone, small_mag_ptr);
4447 CHECK(szone, __PRETTY_FUNCTION__);
4448 ptr = (void *)((uintptr_t)ptr & ~ (SMALL_QUANTUM - 1));
4449 if (cleared_requested) {
4450 memset(ptr, 0, SMALL_BYTES_FOR_MSIZE(msize));
4451 }
4452 return ptr;
4453 }
4454 #endif /* SMALL_CACHE */
4455
4456 ptr = small_malloc_from_free_list(szone, small_mag_ptr, mag_index, msize);
4457 if (ptr) {
4458 SZONE_MAGAZINE_PTR_UNLOCK(szone, small_mag_ptr);
4459 CHECK(szone, __PRETTY_FUNCTION__);
4460 if (cleared_requested) {
4461 memset(ptr, 0, SMALL_BYTES_FOR_MSIZE(msize));
4462 }
4463 return ptr;
4464 }
4465
4466 if (small_get_region_from_depot(szone, small_mag_ptr, mag_index)) {
4467 ptr = small_malloc_from_free_list(szone, small_mag_ptr, mag_index, msize);
4468 if (ptr) {
4469 SZONE_MAGAZINE_PTR_UNLOCK(szone, small_mag_ptr);
4470 CHECK(szone, __PRETTY_FUNCTION__);
4471 if (cleared_requested) {
4472 memset(ptr, 0, SMALL_BYTES_FOR_MSIZE(msize));
4473 }
4474 return ptr;
4475 }
4476 }
4477
4478 ptr = small_malloc_from_region_no_lock(szone, small_mag_ptr, mag_index, msize);
4479 // we don't clear because this freshly allocated space is pristine
4480 SZONE_MAGAZINE_PTR_UNLOCK(szone, small_mag_ptr);
4481 CHECK(szone, __PRETTY_FUNCTION__);
4482 return ptr;
4483 }
4484
4485 static NOINLINE void
4486 free_small_botch(szone_t *szone, free_list_t *ptr)
4487 {
4488 mag_index_t mag_index = MAGAZINE_INDEX_FOR_SMALL_REGION(SMALL_REGION_FOR_PTR(ptr));
4489 magazine_t *small_mag_ptr = &(szone->small_magazines[mag_index]);
4490 SZONE_MAGAZINE_PTR_UNLOCK(szone, small_mag_ptr);
4491 szone_error(szone, 1, "double free", ptr, NULL);
4492 }
4493
4494 static INLINE void
4495 free_small(szone_t *szone, void *ptr, region_t small_region, size_t known_size)
4496 {
4497 msize_t msize;
4498 mag_index_t mag_index = MAGAZINE_INDEX_FOR_SMALL_REGION(SMALL_REGION_FOR_PTR(ptr));
4499 magazine_t *small_mag_ptr = &(szone->small_magazines[mag_index]);
4500
4501 // ptr is known to be in small_region
4502 if (known_size) {
4503 msize = SMALL_MSIZE_FOR_BYTES(known_size + SMALL_QUANTUM - 1);
4504 } else {
4505 msize = SMALL_PTR_SIZE(ptr);
4506 if (SMALL_PTR_IS_FREE(ptr)) {
4507 free_small_botch(szone, ptr);
4508 return;
4509 }
4510 }
4511
4512 SZONE_MAGAZINE_PTR_LOCK(szone, small_mag_ptr);
4513
4514 #if SMALL_CACHE
4515 // Depot does not participate in SMALL_CACHE since it can't be directly malloc()'d
4516 if (DEPOT_MAGAZINE_INDEX != mag_index) {
4517
4518 void *ptr2 = small_mag_ptr->mag_last_free; // Might be NULL
4519 region_t rgn2 = small_mag_ptr->mag_last_free_rgn;
4520
4521 /* check that we don't already have this pointer in the cache */
4522 if (ptr == (void *)((uintptr_t)ptr2 & ~ (SMALL_QUANTUM - 1))) {
4523 free_small_botch(szone, ptr);
4524 return;
4525 }
4526
4527 if ((szone->debug_flags & SCALABLE_MALLOC_DO_SCRIBBLE) && msize)
4528 memset(ptr, 0x55, SMALL_BYTES_FOR_MSIZE(msize));
4529
4530 small_mag_ptr->mag_last_free = (void *)(((uintptr_t)ptr) | msize);
4531 small_mag_ptr->mag_last_free_rgn = small_region;
4532
4533 if (!ptr2) {
4534 SZONE_MAGAZINE_PTR_UNLOCK(szone, small_mag_ptr);
4535 CHECK(szone, __PRETTY_FUNCTION__);
4536 return;
4537 }
4538
4539 msize = (uintptr_t)ptr2 & (SMALL_QUANTUM - 1);
4540 ptr = (void *)(((uintptr_t)ptr2) & ~(SMALL_QUANTUM - 1));
4541 small_region = rgn2;
4542 }
4543 #endif /* SMALL_CACHE */
4544
4545 // Now in the time it took to acquire the lock, the region may have migrated
4546 // from one magazine to another. I.e. trailer->mag_index is volatile.
4547 // In which case the magazine lock we obtained (namely magazines[mag_index].mag_lock)
4548 // is stale. If so, keep on tryin' ...
4549 region_trailer_t *trailer = REGION_TRAILER_FOR_SMALL_REGION(small_region);
4550 mag_index_t refreshed_index;
4551
4552 while (mag_index != (refreshed_index = trailer->mag_index)) { // Note assignment
4553
4554 SZONE_MAGAZINE_PTR_UNLOCK(szone, small_mag_ptr);
4555
4556 mag_index = refreshed_index;
4557 small_mag_ptr = &(szone->small_magazines[mag_index]);
4558 SZONE_MAGAZINE_PTR_LOCK(szone, small_mag_ptr);
4559 }
4560
4561 small_free_no_lock(szone, small_mag_ptr, mag_index, small_region, ptr, msize);
4562 SZONE_MAGAZINE_PTR_UNLOCK(szone, small_mag_ptr);
4563 CHECK(szone, __PRETTY_FUNCTION__);
4564 }
4565
4566 static void
4567 print_small_free_list(szone_t *szone)
4568 {
4569 free_list_t *ptr;
4570 _SIMPLE_STRING b = _simple_salloc();
4571 mag_index_t mag_index;
4572
4573 if (b) {
4574 _simple_sappend(b, "small free sizes:\n");
4575 for (mag_index = -1; mag_index < szone->num_small_magazines; mag_index++) {
4576 grain_t slot = 0;
4577 _simple_sprintf(b,"\tMagazine %d: ", mag_index);
4578 while (slot < szone->num_small_slots) {
4579 ptr = szone->small_magazines[mag_index].mag_free_list[slot];
4580 if (ptr) {
4581 _simple_sprintf(b, "%s%y[%d]; ", (slot == szone->num_small_slots-1) ? ">=" : "",
4582 (slot + 1) * SMALL_QUANTUM, free_list_count(szone, ptr));
4583 }
4584 slot++;
4585 }
4586 _simple_sappend(b,"\n");
4587 }
4588 _malloc_printf(MALLOC_PRINTF_NOLOG | MALLOC_PRINTF_NOPREFIX, "%s\n", _simple_string(b));
4589 _simple_sfree(b);
4590 }
4591 }
4592
4593 static void
4594 print_small_region(szone_t *szone, boolean_t verbose, region_t region, size_t bytes_at_end)
4595 {
4596 unsigned counts[1024];
4597 unsigned in_use = 0;
4598 uintptr_t start = (uintptr_t)SMALL_REGION_ADDRESS(region);
4599 uintptr_t current = start;
4600 uintptr_t limit = (uintptr_t)SMALL_REGION_END(region) - bytes_at_end;
4601 msize_t msize_and_free;
4602 msize_t msize;
4603 unsigned ci;
4604 _SIMPLE_STRING b;
4605 uintptr_t pgTot = 0;
4606
4607 if (region == HASHRING_REGION_DEALLOCATED) {
4608 if ((b = _simple_salloc()) != NULL) {
4609 _simple_sprintf(b, "Small region [unknown address] was returned to the OS\n");
4610 _malloc_printf(MALLOC_PRINTF_NOLOG | MALLOC_PRINTF_NOPREFIX, "%s\n", _simple_string(b));
4611 _simple_sfree(b);
4612 }
4613 return;
4614 }
4615
4616 memset(counts, 0, sizeof(counts));
4617 while (current < limit) {
4618 msize_and_free = *SMALL_METADATA_FOR_PTR(current);
4619 msize = msize_and_free & ~ SMALL_IS_FREE;
4620 if (!msize) {
4621 malloc_printf("*** error with %p: msize=%d\n", (void *)current, (unsigned)msize);
4622 break;
4623 }
4624 if (!(msize_and_free & SMALL_IS_FREE)) {
4625 // block in use
4626 if (msize < 1024)
4627 counts[msize]++;
4628 in_use++;
4629 } else {
4630 uintptr_t pgLo = round_page(current + sizeof(free_list_t) + sizeof(msize_t));
4631 uintptr_t pgHi = trunc_page(current + TINY_BYTES_FOR_MSIZE(msize) - sizeof(msize_t));
4632
4633 if (pgLo < pgHi) {
4634 pgTot += (pgHi - pgLo);
4635 }
4636 }
4637 current += SMALL_BYTES_FOR_MSIZE(msize);
4638 }
4639 if ((b = _simple_salloc()) != NULL) {
4640 _simple_sprintf(b, "Small region [%p-%p, %y] \t", (void *)start, SMALL_REGION_END(region), (int)SMALL_REGION_SIZE);
4641 _simple_sprintf(b, "Magazine=%d \t", MAGAZINE_INDEX_FOR_SMALL_REGION(region));
4642 _simple_sprintf(b, "Allocations in use=%d \t Bytes in use=%ly \t", in_use, BYTES_USED_FOR_SMALL_REGION(region));
4643 if (bytes_at_end)
4644 _simple_sprintf(b, "Untouched=%ly ", bytes_at_end);
4645 if (DEPOT_MAGAZINE_INDEX == MAGAZINE_INDEX_FOR_SMALL_REGION(region)) {
4646 _simple_sprintf(b, "Advised MADV_FREE=%ly", pgTot);
4647 } else {
4648 _simple_sprintf(b, "Fragments subject to reclamation=%ly", pgTot);
4649 }
4650 if (verbose && in_use) {
4651 _simple_sappend(b, "\n\tSizes in use: ");
4652 for (ci = 0; ci < 1024; ci++)
4653 if (counts[ci])
4654 _simple_sprintf(b, "%d[%d] ", SMALL_BYTES_FOR_MSIZE(ci), counts[ci]);
4655 }
4656 _malloc_printf(MALLOC_PRINTF_NOLOG | MALLOC_PRINTF_NOPREFIX, "%s\n", _simple_string(b));
4657 _simple_sfree(b);
4658 }
4659 }
4660
4661 static boolean_t
4662 small_free_list_check(szone_t *szone, grain_t slot)
4663 {
4664 mag_index_t mag_index;
4665
4666 for (mag_index = -1; mag_index < szone->num_small_magazines; mag_index++) {
4667 magazine_t *small_mag_ptr = &(szone->small_magazines[mag_index]);
4668 SZONE_MAGAZINE_PTR_LOCK(szone, small_mag_ptr);
4669
4670 unsigned count = 0;
4671 free_list_t *ptr = szone->small_magazines[mag_index].mag_free_list[slot];
4672 msize_t msize_and_free;
4673 free_list_t *previous = NULL;
4674
4675 while (ptr) {
4676 msize_and_free = *SMALL_METADATA_FOR_PTR(ptr);
4677 if (!(msize_and_free & SMALL_IS_FREE)) {
4678 malloc_printf("*** in-use ptr in free list slot=%d count=%d ptr=%p\n", slot, count, ptr);
4679 SZONE_MAGAZINE_PTR_UNLOCK(szone, small_mag_ptr);
4680 return 0;
4681 }
4682 if (((uintptr_t)ptr) & (SMALL_QUANTUM - 1)) {
4683 malloc_printf("*** unaligned ptr in free list slot=%d count=%d ptr=%p\n", slot, count, ptr);
4684 SZONE_MAGAZINE_PTR_UNLOCK(szone, small_mag_ptr);
4685 return 0;
4686 }
4687 if (!small_region_for_ptr_no_lock(szone, ptr)) {
4688 malloc_printf("*** ptr not in szone slot=%d count=%d ptr=%p\n", slot, count, ptr);
4689 SZONE_MAGAZINE_PTR_UNLOCK(szone, small_mag_ptr);
4690 return 0;
4691 }
4692 if (free_list_unchecksum_ptr(szone, &ptr->previous) != previous) {
4693 malloc_printf("*** previous incorrectly set slot=%d count=%d ptr=%p\n", slot, count, ptr);
4694 SZONE_MAGAZINE_PTR_UNLOCK(szone, small_mag_ptr);
4695 return 0;
4696 }
4697 previous = ptr;
4698 ptr = free_list_unchecksum_ptr(szone, &ptr->next);
4699 count++;
4700 }
4701
4702 SZONE_MAGAZINE_PTR_UNLOCK(szone, small_mag_ptr);
4703 }
4704 return 1;
4705 }
4706
4707 /*******************************************************************************
4708 * Large allocator implementation
4709 ******************************************************************************/
4710 #pragma mark large allocator
4711
4712 #if DEBUG_MALLOC
4713
4714 static void
4715 large_debug_print(szone_t *szone)
4716 {
4717 unsigned index;
4718 large_entry_t *range;
4719 _SIMPLE_STRING b = _simple_salloc();
4720
4721 if (b) {
4722 for (index = 0, range = szone->large_entries; index < szone->num_large_entries; index++, range++)
4723 if (range->address)
4724 _simple_sprintf(b, "%d: %p(%y); ", index, range->address, range->size);
4725
4726 _malloc_printf(MALLOC_PRINTF_NOLOG | MALLOC_PRINTF_NOPREFIX, "%s\n", _simple_string(b));
4727 _simple_sfree(b);
4728 }
4729 }
4730 #endif
4731
4732 /*
4733 * Scan the hash ring looking for an entry for the given pointer.
4734 */
4735 static large_entry_t *
4736 large_entry_for_pointer_no_lock(szone_t *szone, const void *ptr)
4737 {
4738 // result only valid with lock held
4739 unsigned num_large_entries = szone->num_large_entries;
4740 unsigned hash_index;
4741 unsigned index;
4742 large_entry_t *range;
4743
4744 if (!num_large_entries)
4745 return NULL;
4746
4747 hash_index = ((uintptr_t)ptr >> vm_page_shift) % num_large_entries;
4748 index = hash_index;
4749
4750 do {
4751 range = szone->large_entries + index;
4752 if (range->address == (vm_address_t)ptr)
4753 return range;
4754 if (0 == range->address)
4755 return NULL; // end of chain
4756 index++;
4757 if (index == num_large_entries)
4758 index = 0;
4759 } while (index != hash_index);
4760
4761 return NULL;
4762 }
4763
4764 static void
4765 large_entry_insert_no_lock(szone_t *szone, large_entry_t range)
4766 {
4767 unsigned num_large_entries = szone->num_large_entries;
4768 unsigned hash_index = (((uintptr_t)(range.address)) >> vm_page_shift) % num_large_entries;
4769 unsigned index = hash_index;
4770 large_entry_t *entry;
4771
4772 // assert(szone->num_large_objects_in_use < szone->num_large_entries); /* must be called with room to spare */
4773
4774 do {
4775 entry = szone->large_entries + index;
4776 if (0 == entry->address) {
4777 *entry = range;
4778 return; // end of chain
4779 }
4780 index++;
4781 if (index == num_large_entries)
4782 index = 0;
4783 } while (index != hash_index);
4784
4785 // assert(0); /* must not fallthrough! */
4786 }
4787
4788 // FIXME: can't we simply swap the (now empty) entry with the last entry on the collision chain for this hash slot?
4789 static INLINE void
4790 large_entries_rehash_after_entry_no_lock(szone_t *szone, large_entry_t *entry)
4791 {
4792 unsigned num_large_entries = szone->num_large_entries;
4793 unsigned hash_index = entry - szone->large_entries;
4794 unsigned index = hash_index;
4795 large_entry_t range;
4796
4797 // assert(entry->address == 0) /* caller must have cleared *entry */
4798
4799 do {
4800 index++;
4801 if (index == num_large_entries)
4802 index = 0;
4803 range = szone->large_entries[index];
4804 if (0 == range.address)
4805 return;
4806 szone->large_entries[index].address = (vm_address_t)0;
4807 szone->large_entries[index].size = 0;
4808 szone->large_entries[index].did_madvise_reusable = FALSE;
4809 large_entry_insert_no_lock(szone, range); // this will reinsert in the
4810 // proper place
4811 } while (index != hash_index);
4812
4813 // assert(0); /* since entry->address == 0, must not fallthrough! */
4814 }
4815
4816 // FIXME: num should probably be a size_t, since you can theoretically allocate
4817 // more than 2^32-1 large_threshold objects in 64 bit.
4818 static INLINE large_entry_t *
4819 large_entries_alloc_no_lock(szone_t *szone, unsigned num)
4820 {
4821 size_t size = num * sizeof(large_entry_t);
4822
4823 // Note that we allocate memory (via a system call) under a spin lock
4824 // That is certainly evil, however it's very rare in the lifetime of a process
4825 // The alternative would slow down the normal case
4826 return allocate_pages(szone, round_page(size), 0, 0, VM_MEMORY_MALLOC_LARGE);
4827 }
4828
4829 static void
4830 large_entries_free_no_lock(szone_t *szone, large_entry_t *entries, unsigned num, vm_range_t *range_to_deallocate)
4831 {
4832 size_t size = num * sizeof(large_entry_t);
4833
4834 range_to_deallocate->address = (vm_address_t)entries;
4835 range_to_deallocate->size = round_page(size);
4836 }
4837
4838 static large_entry_t *
4839 large_entries_grow_no_lock(szone_t *szone, vm_range_t *range_to_deallocate)
4840 {
4841 // sets range_to_deallocate
4842 unsigned old_num_entries = szone->num_large_entries;
4843 large_entry_t *old_entries = szone->large_entries;
4844 // always an odd number for good hashing
4845 unsigned new_num_entries = (old_num_entries) ? old_num_entries * 2 + 1 :
4846 ((vm_page_size / sizeof(large_entry_t)) - 1);
4847 large_entry_t *new_entries = large_entries_alloc_no_lock(szone, new_num_entries);
4848 unsigned index = old_num_entries;
4849 large_entry_t oldRange;
4850
4851 // if the allocation of new entries failed, bail
4852 if (new_entries == NULL)
4853 return NULL;
4854
4855 szone->num_large_entries = new_num_entries;
4856 szone->large_entries = new_entries;
4857
4858 /* rehash entries into the new list */
4859 while (index--) {
4860 oldRange = old_entries[index];
4861 if (oldRange.address) {
4862 large_entry_insert_no_lock(szone, oldRange);
4863 }
4864 }
4865
4866 if (old_entries) {
4867 large_entries_free_no_lock(szone, old_entries, old_num_entries, range_to_deallocate);
4868 } else {
4869 range_to_deallocate->address = (vm_address_t)0;
4870 range_to_deallocate->size = 0;
4871 }
4872
4873 return new_entries;
4874 }
4875
4876 // frees the specific entry in the size table
4877 // returns a range to truly deallocate
4878 static vm_range_t
4879 large_entry_free_no_lock(szone_t *szone, large_entry_t *entry)
4880 {
4881 vm_range_t range;
4882
4883 range.address = entry->address;
4884 range.size = entry->size;
4885
4886 if (szone->debug_flags & SCALABLE_MALLOC_ADD_GUARD_PAGES) {
4887 protect((void *)range.address, range.size, VM_PROT_READ | VM_PROT_WRITE, szone->debug_flags);
4888 range.address -= vm_page_size;
4889 range.size += 2 * vm_page_size;
4890 }
4891
4892 entry->address = 0;
4893 entry->size = 0;
4894 entry->did_madvise_reusable = FALSE;
4895 large_entries_rehash_after_entry_no_lock(szone, entry);
4896
4897 #if DEBUG_MALLOC
4898 if (large_entry_for_pointer_no_lock(szone, (void *)range.address)) {
4899 malloc_printf("*** freed entry %p still in use; num_large_entries=%d\n",
4900 range.address, szone->num_large_entries);
4901 large_debug_print(szone);
4902 szone_sleep();
4903 }
4904 #endif
4905 return range;
4906 }
4907
4908 static NOINLINE kern_return_t
4909 large_in_use_enumerator(task_t task, void *context, unsigned type_mask, vm_address_t large_entries_address,
4910 unsigned num_entries, memory_reader_t reader, vm_range_recorder_t recorder)
4911 {
4912 unsigned index = 0;
4913 vm_range_t buffer[MAX_RECORDER_BUFFER];
4914 unsigned count = 0;
4915 large_entry_t *entries;
4916 kern_return_t err;
4917 vm_range_t range;
4918 large_entry_t entry;
4919
4920 err = reader(task, large_entries_address, sizeof(large_entry_t) * num_entries, (void **)&entries);
4921 if (err)
4922 return err;
4923
4924 index = num_entries;
4925 if (type_mask & MALLOC_ADMIN_REGION_RANGE_TYPE) {
4926 range.address = large_entries_address;
4927 range.size = round_page(num_entries * sizeof(large_entry_t));
4928 recorder(task, context, MALLOC_ADMIN_REGION_RANGE_TYPE, &range, 1);
4929 }
4930 if (type_mask & (MALLOC_PTR_IN_USE_RANGE_TYPE | MALLOC_PTR_REGION_RANGE_TYPE)) {
4931 while (index--) {
4932 entry = entries[index];
4933 if (entry.address) {
4934 range.address = entry.address;
4935 range.size = entry.size;
4936 buffer[count++] = range;
4937 if (count >= MAX_RECORDER_BUFFER) {
4938 recorder(task, context, MALLOC_PTR_IN_USE_RANGE_TYPE | MALLOC_PTR_REGION_RANGE_TYPE,
4939 buffer, count);
4940 count = 0;
4941 }
4942 }
4943 }
4944 }
4945 if (count) {
4946 recorder(task, context, MALLOC_PTR_IN_USE_RANGE_TYPE | MALLOC_PTR_REGION_RANGE_TYPE,
4947 buffer, count);
4948 }
4949 return 0;
4950 }
4951
4952 static void *
4953 large_malloc(szone_t *szone, size_t num_pages, unsigned char alignment,
4954 boolean_t cleared_requested)
4955 {
4956 void *addr;
4957 vm_range_t range_to_deallocate;
4958 size_t size;
4959 large_entry_t large_entry;
4960
4961 if (!num_pages)
4962 num_pages = 1; // minimal allocation size for this szone
4963 size = (size_t)num_pages << vm_page_shift;
4964 range_to_deallocate.size = 0;
4965 range_to_deallocate.address = 0;
4966
4967 #if LARGE_CACHE
4968 if (size < LARGE_CACHE_SIZE_ENTRY_LIMIT) { // Look for a large_entry_t on the death-row cache?
4969 SZONE_LOCK(szone);
4970
4971 int i, best = -1, idx = szone->large_entry_cache_newest, stop_idx = szone->large_entry_cache_oldest;
4972 size_t best_size = SIZE_T_MAX;
4973
4974 while (1) { // Scan large_entry_cache for best fit, starting with most recent entry
4975 size_t this_size = szone->large_entry_cache[idx].size;
4976
4977 if (size == this_size) { // size match!
4978 best = idx;
4979 best_size = this_size;
4980 break;
4981 }
4982
4983 if (size <= this_size && this_size < best_size) { // improved fit?
4984 best = idx;
4985 best_size = this_size;
4986 }
4987
4988 if (idx == stop_idx) // exhausted live ring?
4989 break;
4990
4991 if (idx)
4992 idx--; // bump idx down
4993 else
4994 idx = LARGE_ENTRY_CACHE_SIZE - 1; // wrap idx
4995 }
4996
4997 if (best > -1 && (best_size - size) < size) { //limit fragmentation to 50%
4998 addr = (void *)szone->large_entry_cache[best].address;
4999 boolean_t was_madvised_reusable = szone->large_entry_cache[best].did_madvise_reusable;
5000
5001 // Compact live ring to fill entry now vacated at large_entry_cache[best]
5002 // while preserving time-order
5003 if (szone->large_entry_cache_oldest < szone->large_entry_cache_newest) {
5004
5005 // Ring hasn't wrapped. Fill in from right.
5006 for (i = best; i < szone->large_entry_cache_newest; ++i)
5007 szone->large_entry_cache[i] = szone->large_entry_cache[i + 1];
5008
5009 szone->large_entry_cache_newest--; // Pull in right endpoint.
5010
5011 } else if (szone->large_entry_cache_newest < szone->large_entry_cache_oldest) {
5012
5013 // Ring has wrapped. Arrange to fill in from the contiguous side.
5014 if (best <= szone->large_entry_cache_newest) {
5015 // Fill from right.
5016 for (i = best; i < szone->large_entry_cache_newest; ++i)
5017 szone->large_entry_cache[i] = szone->large_entry_cache[i + 1];
5018
5019 if (0 < szone->large_entry_cache_newest)
5020 szone->large_entry_cache_newest--;
5021 else
5022 szone->large_entry_cache_newest = LARGE_ENTRY_CACHE_SIZE - 1;
5023 } else {
5024 // Fill from left.
5025 for ( i = best; i > szone->large_entry_cache_oldest; --i)
5026 szone->large_entry_cache[i] = szone->large_entry_cache[i - 1];
5027
5028 if (szone->large_entry_cache_oldest < LARGE_ENTRY_CACHE_SIZE - 1)
5029 szone->large_entry_cache_oldest++;
5030 else
5031 szone->large_entry_cache_oldest = 0;
5032 }
5033
5034 } else {
5035 // By trichotomy, large_entry_cache_newest == large_entry_cache_oldest.
5036 // That implies best == large_entry_cache_newest == large_entry_cache_oldest
5037 // and the ring is now empty.
5038 szone->large_entry_cache[best].address = 0;
5039 szone->large_entry_cache[best].size = 0;
5040 szone->large_entry_cache[best].did_madvise_reusable = FALSE;
5041 }
5042
5043 if ((szone->num_large_objects_in_use + 1) * 4 > szone->num_large_entries) {
5044 // density of hash table too high; grow table
5045 // we do that under lock to avoid a race
5046 large_entry_t *entries = large_entries_grow_no_lock(szone, &range_to_deallocate);
5047 if (entries == NULL) {
5048 SZONE_UNLOCK(szone);
5049 return NULL;
5050 }
5051 }
5052
5053 large_entry.address = (vm_address_t)addr;
5054 large_entry.size = best_size;
5055 large_entry.did_madvise_reusable = FALSE;
5056 large_entry_insert_no_lock(szone, large_entry);
5057
5058 szone->num_large_objects_in_use ++;
5059 szone->num_bytes_in_large_objects += best_size;
5060 if (!was_madvised_reusable)
5061 szone->large_entry_cache_hoard_bytes -= best_size;
5062 SZONE_UNLOCK(szone);
5063
5064 if (range_to_deallocate.size) {
5065 // we deallocate outside the lock
5066 deallocate_pages(szone, (void *)range_to_deallocate.address, range_to_deallocate.size, 0);
5067 }
5068
5069 // Perform the madvise() outside the lock.
5070 // Typically the madvise() is successful and we'll quickly return from this routine.
5071 // In the unusual case of failure, reacquire the lock to unwind.
5072 if (was_madvised_reusable && -1 == madvise(addr, size, MADV_FREE_REUSE)) {
5073 /* -1 return: VM map entry change makes this unfit for reuse. */
5074 #if DEBUG_MALLOC
5075 szone_error(szone, 1, "large_malloc madvise(..., MADV_FREE_REUSE) failed", addr, NULL);
5076 #endif
5077
5078 SZONE_LOCK(szone);
5079 szone->num_large_objects_in_use--;
5080 szone->num_bytes_in_large_objects -= large_entry.size;
5081
5082 // Re-acquire "entry" after interval just above where we let go the lock.
5083 large_entry_t *entry = large_entry_for_pointer_no_lock(szone, addr);
5084 if (NULL == entry) {
5085 szone_error(szone, 1, "entry for pointer being discarded from death-row vanished", addr, NULL);
5086 SZONE_UNLOCK(szone);
5087 } else {
5088
5089 range_to_deallocate = large_entry_free_no_lock(szone, entry);
5090 SZONE_UNLOCK(szone);
5091
5092 if (range_to_deallocate.size) {
5093 // we deallocate outside the lock
5094 deallocate_pages(szone, (void *)range_to_deallocate.address, range_to_deallocate.size, 0);
5095 }
5096 }
5097 /* Fall through to allocate_pages() afresh. */
5098 } else {
5099 if (cleared_requested) {
5100 memset(addr, 0, size);
5101 }
5102
5103 return addr;
5104 }
5105 } else {
5106 SZONE_UNLOCK(szone);
5107 }
5108 }
5109
5110 range_to_deallocate.size = 0;
5111 range_to_deallocate.address = 0;
5112 #endif /* LARGE_CACHE */
5113
5114 addr = allocate_pages(szone, size, alignment, szone->debug_flags, VM_MEMORY_MALLOC_LARGE);
5115 if (addr == NULL) {
5116 return NULL;
5117 }
5118
5119 SZONE_LOCK(szone);
5120 if ((szone->num_large_objects_in_use + 1) * 4 > szone->num_large_entries) {
5121 // density of hash table too high; grow table
5122 // we do that under lock to avoid a race
5123 large_entry_t *entries = large_entries_grow_no_lock(szone, &range_to_deallocate);
5124 if (entries == NULL) {
5125 SZONE_UNLOCK(szone);
5126 return NULL;
5127 }
5128 }
5129
5130 large_entry.address = (vm_address_t)addr;
5131 large_entry.size = size;
5132 large_entry.did_madvise_reusable = FALSE;
5133 large_entry_insert_no_lock(szone, large_entry);
5134
5135 szone->num_large_objects_in_use ++;
5136 szone->num_bytes_in_large_objects += size;
5137 SZONE_UNLOCK(szone);
5138
5139 if (range_to_deallocate.size) {
5140 // we deallocate outside the lock
5141 deallocate_pages(szone, (void *)range_to_deallocate.address, range_to_deallocate.size, 0);
5142 }
5143 return addr;
5144 }
5145
5146 static NOINLINE void
5147 free_large(szone_t *szone, void *ptr)
5148 {
5149 // We have established ptr is page-aligned and neither tiny nor small
5150 large_entry_t *entry;
5151 vm_range_t vm_range_to_deallocate;
5152
5153 SZONE_LOCK(szone);
5154 entry = large_entry_for_pointer_no_lock(szone, ptr);
5155 if (entry) {
5156 #if LARGE_CACHE
5157 #ifndef MADV_CAN_REUSE
5158 #define MADV_CAN_REUSE 9 /* per Francois, for testing until xnu is resubmitted to B&I */
5159 #endif
5160 if (entry->size < LARGE_CACHE_SIZE_ENTRY_LIMIT &&
5161 -1 != madvise((void *)(entry->address), entry->size, MADV_CAN_REUSE)) { // Put the large_entry_t on the death-row cache?
5162 int idx = szone->large_entry_cache_newest, stop_idx = szone->large_entry_cache_oldest;
5163 large_entry_t this_entry = *entry; // Make a local copy, "entry" is volatile when lock is let go.
5164 boolean_t reusable = TRUE;
5165 boolean_t should_madvise = szone->large_entry_cache_hoard_bytes + this_entry.size > szone->large_entry_cache_hoard_lmit;
5166
5167 // Already freed?
5168 // [Note that repeated entries in death-row risk vending the same entry subsequently
5169 // to two different malloc() calls. By checking here the (illegal) double free
5170 // is accommodated, matching the behavior of the previous implementation.]
5171 while (1) { // Scan large_entry_cache starting with most recent entry
5172 if (szone->large_entry_cache[idx].address == entry->address) {
5173 szone_error(szone, 1, "pointer being freed already on death-row", ptr, NULL);
5174 SZONE_UNLOCK(szone);
5175 return;
5176 }
5177
5178 if (idx == stop_idx) // exhausted live ring?
5179 break;
5180
5181 if (idx)
5182 idx--; // bump idx down
5183 else
5184 idx = LARGE_ENTRY_CACHE_SIZE - 1; // wrap idx
5185 }
5186
5187 SZONE_UNLOCK(szone);
5188
5189 if (szone->debug_flags & SCALABLE_MALLOC_PURGEABLE) { // Are we a purgable zone?
5190 int state = VM_PURGABLE_NONVOLATILE; // restore to default condition
5191
5192 if (KERN_SUCCESS != vm_purgable_control(mach_task_self(), this_entry.address, VM_PURGABLE_SET_STATE, &state)) {
5193 malloc_printf("*** can't vm_purgable_control(..., VM_PURGABLE_SET_STATE) for large freed block at %p\n", this_entry.address);
5194 reusable = FALSE;
5195 }
5196 }
5197
5198 if (szone->large_legacy_reset_mprotect) { // Linked for Leopard?
5199 // Accomodate Leopard apps that (illegally) mprotect() their own guard pages on large malloc'd allocations
5200 kern_return_t err = vm_protect(mach_task_self(), (vm_address_t)(this_entry.address), this_entry.size,
5201 0, PROT_READ | PROT_WRITE);
5202 if (err) {
5203 malloc_printf("*** can't reset protection for large freed block at %p\n", this_entry.address);
5204 reusable = FALSE;
5205 }
5206 }
5207
5208 // madvise(..., MADV_REUSABLE) death-row arrivals if hoarding would exceed large_entry_cache_hoard_lmit
5209 if (should_madvise) {
5210 // Issue madvise to avoid paging out the dirtied free()'d pages in "entry"
5211 MAGMALLOC_MADVFREEREGION((void *)szone, (void *)0, (void *)(this_entry.address), this_entry.size); // DTrace USDT Probe
5212
5213 if (-1 == madvise((void *)(this_entry.address), this_entry.size, MADV_FREE_REUSABLE)) {
5214 /* -1 return: VM map entry change makes this unfit for reuse. */
5215 #if DEBUG_MALLOC
5216 szone_error(szone, 1, "free_large madvise(..., MADV_FREE_REUSABLE) failed", (void *)this_entry.address, NULL);
5217 #endif
5218 reusable = FALSE;
5219 }
5220 }
5221
5222 SZONE_LOCK(szone);
5223
5224 // Re-acquire "entry" after interval just above where we let go the lock.
5225 entry = large_entry_for_pointer_no_lock(szone, ptr);
5226 if (NULL == entry) {
5227 szone_error(szone, 1, "entry for pointer being freed from death-row vanished", ptr, NULL);
5228 SZONE_UNLOCK(szone);
5229 return;
5230 }
5231
5232 // Add "entry" to death-row ring
5233 if (reusable) {
5234 int idx = szone->large_entry_cache_newest; // Most recently occupied
5235 vm_address_t addr;
5236 size_t adjsize;
5237
5238 if (szone->large_entry_cache_newest == szone->large_entry_cache_oldest &&
5239 0 == szone->large_entry_cache[idx].address) {
5240 // Ring is empty, idx is good as it stands
5241 addr = 0;
5242 adjsize = 0;
5243 } else {
5244 // Extend the queue to the "right" by bumping up large_entry_cache_newest
5245 if (idx == LARGE_ENTRY_CACHE_SIZE - 1)
5246 idx = 0; // Wrap index
5247 else
5248 idx++; // Bump index
5249
5250 if (idx == szone->large_entry_cache_oldest) { // Fully occupied
5251 // Drop this entry from the cache and deallocate the VM
5252 addr = szone->large_entry_cache[idx].address;
5253 adjsize = szone->large_entry_cache[idx].size;
5254 if (!szone->large_entry_cache[idx].did_madvise_reusable)
5255 szone->large_entry_cache_hoard_bytes -= adjsize;
5256 } else {
5257 // Using an unoccupied cache slot
5258 addr = 0;
5259 adjsize = 0;
5260 }
5261 }
5262
5263 if ((szone->debug_flags & SCALABLE_MALLOC_DO_SCRIBBLE))
5264 memset((void *)(entry->address), 0x55, entry->size);
5265
5266 entry->did_madvise_reusable = should_madvise; // Was madvise()'d above?
5267 if (!should_madvise) // Entered on death-row without madvise() => up the hoard total
5268 szone->large_entry_cache_hoard_bytes += entry->size;
5269
5270 szone->large_entry_cache[idx] = *entry;
5271 szone->large_entry_cache_newest = idx;
5272
5273 szone->num_large_objects_in_use--;
5274 szone->num_bytes_in_large_objects -= entry->size;
5275
5276 (void)large_entry_free_no_lock(szone, entry);
5277
5278 if (0 == addr) {
5279 SZONE_UNLOCK(szone);
5280 return;
5281 }
5282
5283 // Fall through to drop large_entry_cache_oldest from the cache,
5284 // and then deallocate its pages.
5285
5286 // Trim the queue on the "left" by bumping up large_entry_cache_oldest
5287 if (szone->large_entry_cache_oldest == LARGE_ENTRY_CACHE_SIZE - 1)
5288 szone->large_entry_cache_oldest = 0;
5289 else
5290 szone->large_entry_cache_oldest++;
5291
5292 // we deallocate_pages, including guard pages, outside the lock
5293 SZONE_UNLOCK(szone);
5294 deallocate_pages(szone, (void *)addr, (size_t)adjsize, 0);
5295 return;
5296 } else {
5297 /* fall through to discard an allocation that is not reusable */
5298 }
5299 }
5300 #endif /* LARGE_CACHE */
5301
5302 szone->num_large_objects_in_use--;
5303 szone->num_bytes_in_large_objects -= entry->size;
5304
5305 vm_range_to_deallocate = large_entry_free_no_lock(szone, entry);
5306 } else {
5307 #if DEBUG_MALLOC
5308 large_debug_print(szone);
5309 #endif
5310 szone_error(szone, 1, "pointer being freed was not allocated", ptr, NULL);
5311 SZONE_UNLOCK(szone);
5312 return;
5313 }
5314 SZONE_UNLOCK(szone); // we release the lock asap
5315 CHECK(szone, __PRETTY_FUNCTION__);
5316
5317 // we deallocate_pages, including guard pages, outside the lock
5318 if (vm_range_to_deallocate.address) {
5319 #if DEBUG_MALLOC
5320 // FIXME: large_entry_for_pointer_no_lock() needs the lock held ...
5321 if (large_entry_for_pointer_no_lock(szone, (void *)vm_range_to_deallocate.address)) {
5322 malloc_printf("*** invariant broken: %p still in use num_large_entries=%d\n",
5323 vm_range_to_deallocate.address, szone->num_large_entries);
5324 large_debug_print(szone);
5325 szone_sleep();
5326 }
5327 #endif
5328 deallocate_pages(szone, (void *)vm_range_to_deallocate.address, (size_t)vm_range_to_deallocate.size, 0);
5329 }
5330 }
5331
5332 static INLINE int
5333 large_try_realloc_in_place(szone_t *szone, void *ptr, size_t old_size, size_t new_size)
5334 {
5335 vm_address_t addr = (vm_address_t)ptr + old_size;
5336 large_entry_t *large_entry;
5337 kern_return_t err;
5338
5339 SZONE_LOCK(szone);
5340 large_entry = large_entry_for_pointer_no_lock(szone, (void *)addr);
5341 SZONE_UNLOCK(szone);
5342
5343 if (large_entry) { // check if "addr = ptr + old_size" is already spoken for
5344 return 0; // large pointer already exists in table - extension is not going to work
5345 }
5346
5347 new_size = round_page(new_size);
5348 /*
5349 * Ask for allocation at a specific address, and mark as realloc
5350 * to request coalescing with previous realloc'ed extensions.
5351 */
5352 err = vm_allocate(mach_task_self(), &addr, new_size - old_size, VM_MAKE_TAG(VM_MEMORY_REALLOC));
5353 if (err != KERN_SUCCESS) {
5354 return 0;
5355 }
5356
5357 SZONE_LOCK(szone);
5358 /* extend existing large entry */
5359 large_entry = large_entry_for_pointer_no_lock(szone, ptr);
5360 if (!large_entry) {
5361 szone_error(szone, 1, "large entry reallocated is not properly in table", ptr, NULL);
5362 SZONE_UNLOCK(szone);
5363 return 0; // Bail, leaking "addr"
5364 }
5365
5366 large_entry->address = (vm_address_t)ptr;
5367 large_entry->size = new_size;
5368 szone->num_bytes_in_large_objects += new_size - old_size;
5369 SZONE_UNLOCK(szone); // we release the lock asap
5370
5371 return 1;
5372 }
5373
5374 /********************* Zone call backs ************************/
5375 /*
5376 * Mark these NOINLINE to avoid bloating the purgeable zone call backs
5377 */
5378 static NOINLINE void
5379 szone_free(szone_t *szone, void *ptr)
5380 {
5381 region_t tiny_region;
5382 region_t small_region;
5383
5384 #if DEBUG_MALLOC
5385 if (LOG(szone, ptr))
5386 malloc_printf("in szone_free with %p\n", ptr);
5387 #endif
5388 if (!ptr)
5389 return;
5390 /*
5391 * Try to free to a tiny region.
5392 */
5393 if ((uintptr_t)ptr & (TINY_QUANTUM - 1)) {
5394 szone_error(szone, 1, "Non-aligned pointer being freed", ptr, NULL);
5395 return;
5396 }
5397 if ((tiny_region = tiny_region_for_ptr_no_lock(szone, ptr)) != NULL) {
5398 if (TINY_INDEX_FOR_PTR(ptr) >= NUM_TINY_BLOCKS) {
5399 szone_error(szone, 1, "Pointer to metadata being freed", ptr, NULL);
5400 return;
5401 }
5402 free_tiny(szone, ptr, tiny_region, 0);
5403 return;
5404 }
5405
5406 /*
5407 * Try to free to a small region.
5408 */
5409 if ((uintptr_t)ptr & (SMALL_QUANTUM - 1)) {
5410 szone_error(szone, 1, "Non-aligned pointer being freed (2)", ptr, NULL);
5411 return;
5412 }
5413 if ((small_region = small_region_for_ptr_no_lock(szone, ptr)) != NULL) {
5414 if (SMALL_META_INDEX_FOR_PTR(ptr) >= NUM_SMALL_BLOCKS) {
5415 szone_error(szone, 1, "Pointer to metadata being freed (2)", ptr, NULL);
5416 return;
5417 }
5418 free_small(szone, ptr, small_region, 0);
5419 return;
5420 }
5421
5422 /* check that it's a legal large allocation */
5423 if ((uintptr_t)ptr & (vm_page_size - 1)) {
5424 szone_error(szone, 1, "non-page-aligned, non-allocated pointer being freed", ptr, NULL);
5425 return;
5426 }
5427 free_large(szone, ptr);
5428 }
5429
5430 static NOINLINE void
5431 szone_free_definite_size(szone_t *szone, void *ptr, size_t size)
5432 {
5433 #if DEBUG_MALLOC
5434 if (LOG(szone, ptr))
5435 malloc_printf("in szone_free_definite_size with %p\n", ptr);
5436
5437 if (0 == size) {
5438 szone_error(szone, 1, "pointer of size zero being freed", ptr, NULL);
5439 return;
5440 }
5441
5442 #endif
5443 if (!ptr)
5444 return;
5445
5446 /*
5447 * Try to free to a tiny region.
5448 */
5449 if ((uintptr_t)ptr & (TINY_QUANTUM - 1)) {
5450 szone_error(szone, 1, "Non-aligned pointer being freed", ptr, NULL);
5451 return;
5452 }
5453 if (size <= (NUM_TINY_SLOTS - 1)*TINY_QUANTUM) {
5454 if (TINY_INDEX_FOR_PTR(ptr) >= NUM_TINY_BLOCKS) {
5455 szone_error(szone, 1, "Pointer to metadata being freed", ptr, NULL);
5456 return;
5457 }
5458 free_tiny(szone, ptr, TINY_REGION_FOR_PTR(ptr), size);
5459 return;
5460 }
5461
5462 /*
5463 * Try to free to a small region.
5464 */
5465 if ((uintptr_t)ptr & (SMALL_QUANTUM - 1)) {
5466 szone_error(szone, 1, "Non-aligned pointer being freed (2)", ptr, NULL);
5467 return;
5468 }
5469 if (!((szone->debug_flags & SCALABLE_MALLOC_ADD_GUARD_PAGES) && PROTECT_SMALL) &&
5470 (size <= szone->large_threshold)) {
5471 if (SMALL_META_INDEX_FOR_PTR(ptr) >= NUM_SMALL_BLOCKS) {
5472 szone_error(szone, 1, "Pointer to metadata being freed (2)", ptr, NULL);
5473 return;
5474 }
5475 free_small(szone, ptr, SMALL_REGION_FOR_PTR(ptr), size);
5476 return;
5477 }
5478
5479 /* check that it's a legal large allocation */
5480 if ((uintptr_t)ptr & (vm_page_size - 1)) {
5481 szone_error(szone, 1, "non-page-aligned, non-allocated pointer being freed", ptr, NULL);
5482 return;
5483 }
5484 free_large(szone, ptr);
5485 }
5486
5487 static NOINLINE void *
5488 szone_malloc_should_clear(szone_t *szone, size_t size, boolean_t cleared_requested)
5489 {
5490 void *ptr;
5491 msize_t msize;
5492
5493 if (size <= (NUM_TINY_SLOTS - 1)*TINY_QUANTUM) {
5494 // think tiny
5495 msize = TINY_MSIZE_FOR_BYTES(size + TINY_QUANTUM - 1);
5496 if (!msize)
5497 msize = 1;
5498 ptr = tiny_malloc_should_clear(szone, msize, cleared_requested);
5499 } else if (!((szone->debug_flags & SCALABLE_MALLOC_ADD_GUARD_PAGES) && PROTECT_SMALL) &&
5500 (size <= szone->large_threshold)) {
5501 // think small
5502 msize = SMALL_MSIZE_FOR_BYTES(size + SMALL_QUANTUM - 1);
5503 if (! msize)
5504 msize = 1;
5505 ptr = small_malloc_should_clear(szone, msize, cleared_requested);
5506 } else {
5507 // large
5508 size_t num_pages = round_page(size) >> vm_page_shift;
5509 if (num_pages == 0) /* Overflowed */
5510 ptr = 0;
5511 else
5512 ptr = large_malloc(szone, num_pages, 0, cleared_requested);
5513 }
5514 #if DEBUG_MALLOC
5515 if (LOG(szone, ptr))
5516 malloc_printf("szone_malloc returned %p\n", ptr);
5517 #endif
5518 /*
5519 * If requested, scribble on allocated memory.
5520 */
5521 if ((szone->debug_flags & SCALABLE_MALLOC_DO_SCRIBBLE) && ptr && !cleared_requested && size)
5522 memset(ptr, 0xaa, size);
5523
5524 return ptr;
5525 }
5526
5527 static NOINLINE void *
5528 szone_malloc(szone_t *szone, size_t size) {
5529 return szone_malloc_should_clear(szone, size, 0);
5530 }
5531
5532 static NOINLINE void *
5533 szone_calloc(szone_t *szone, size_t num_items, size_t size)
5534 {
5535 size_t total_bytes = num_items * size;
5536
5537 // Check for overflow of integer multiplication
5538 if (num_items > 1) {
5539 #if __LP64__ /* size_t is uint64_t */
5540 if ((num_items | size) & 0xffffffff00000000ul) {
5541 // num_items or size equals or exceeds sqrt(2^64) == 2^32, appeal to wider arithmetic
5542 __uint128_t product = ((__uint128_t)num_items) * ((__uint128_t)size);
5543 if ((uint64_t)(product >> 64)) // compiles to test on upper register of register pair
5544 return NULL;
5545 }
5546 #else /* size_t is uint32_t */
5547 if ((num_items | size) & 0xffff0000ul) {
5548 // num_items or size equals or exceeds sqrt(2^32) == 2^16, appeal to wider arithmetic
5549 uint64_t product = ((uint64_t)num_items) * ((uint64_t)size);
5550 if ((uint32_t)(product >> 32)) // compiles to test on upper register of register pair
5551 return NULL;
5552 }
5553 #endif
5554 }
5555
5556 return szone_malloc_should_clear(szone, total_bytes, 1);
5557 }
5558
5559 static NOINLINE void *
5560 szone_valloc(szone_t *szone, size_t size)
5561 {
5562 void *ptr;
5563
5564 if (size <= szone->large_threshold) {
5565 ptr = szone_memalign(szone, vm_page_size, size);
5566 } else {
5567 size_t num_pages;
5568
5569 num_pages = round_page(size) >> vm_page_shift;
5570 ptr = large_malloc(szone, num_pages, 0, 0);
5571 }
5572
5573 #if DEBUG_MALLOC
5574 if (LOG(szone, ptr))
5575 malloc_printf("szone_valloc returned %p\n", ptr);
5576 #endif
5577 return ptr;
5578 }
5579
5580 /* Isolate PIC-base load (for __is_threaded) here. */
5581 static NOINLINE size_t
5582 szone_size_try_large(szone_t *szone, const void *ptr)
5583 {
5584 size_t size = 0;
5585 large_entry_t *entry;
5586
5587 SZONE_LOCK(szone);
5588 entry = large_entry_for_pointer_no_lock(szone, ptr);
5589 if (entry) {
5590 size = entry->size;
5591 }
5592 SZONE_UNLOCK(szone);
5593 #if DEBUG_MALLOC
5594 if (LOG(szone, ptr)) {
5595 malloc_printf("szone_size for %p returned %d\n", ptr, (unsigned)size);
5596 }
5597 #endif
5598 return size;
5599 }
5600
5601 static NOINLINE size_t
5602 szone_size(szone_t *szone, const void *ptr)
5603 {
5604 boolean_t is_free;
5605 msize_t msize, msize_and_free;
5606
5607 if (!ptr)
5608 return 0;
5609 #if DEBUG_MALLOC
5610 if (LOG(szone, ptr)) {
5611 malloc_printf("in szone_size for %p (szone=%p)\n", ptr, szone);
5612 }
5613 #endif
5614
5615 /*
5616 * Look for it in a tiny region.
5617 */
5618 if ((uintptr_t)ptr & (TINY_QUANTUM - 1))
5619 return 0;
5620 if (tiny_region_for_ptr_no_lock(szone, ptr)) {
5621 if (TINY_INDEX_FOR_PTR(ptr) >= NUM_TINY_BLOCKS)
5622 return 0;
5623 msize = get_tiny_meta_header(ptr, &is_free);
5624 if (is_free)
5625 return 0;
5626 #if TINY_CACHE
5627 {
5628 mag_index_t mag_index = MAGAZINE_INDEX_FOR_TINY_REGION(TINY_REGION_FOR_PTR(ptr));
5629 magazine_t *tiny_mag_ptr = &(szone->tiny_magazines[mag_index]);
5630
5631 if (msize < TINY_QUANTUM && ptr == (void *)((uintptr_t)(tiny_mag_ptr->mag_last_free) & ~ (TINY_QUANTUM - 1)))
5632 return 0;
5633 }
5634 #endif
5635 return TINY_BYTES_FOR_MSIZE(msize);
5636 }
5637
5638 /*
5639 * Look for it in a small region.
5640 */
5641 if ((uintptr_t)ptr & (SMALL_QUANTUM - 1))
5642 return 0;
5643 if (small_region_for_ptr_no_lock(szone, ptr)) {
5644 if (SMALL_META_INDEX_FOR_PTR(ptr) >= NUM_SMALL_BLOCKS)
5645 return 0;
5646 msize_and_free = *SMALL_METADATA_FOR_PTR(ptr);
5647 if (msize_and_free & SMALL_IS_FREE)
5648 return 0;
5649 #if SMALL_CACHE
5650 {
5651 mag_index_t mag_index = MAGAZINE_INDEX_FOR_SMALL_REGION(SMALL_REGION_FOR_PTR(ptr));
5652 magazine_t *small_mag_ptr = &(szone->small_magazines[mag_index]);
5653
5654 if (ptr == (void *)((uintptr_t)(small_mag_ptr->mag_last_free) & ~ (SMALL_QUANTUM - 1)))
5655 return 0;
5656 }
5657 #endif
5658 return SMALL_BYTES_FOR_MSIZE(msize_and_free);
5659 }
5660
5661 /*
5662 * If not page-aligned, it cannot have come from a large allocation.
5663 */
5664 if ((uintptr_t)ptr & (vm_page_size - 1))
5665 return 0;
5666
5667 /*
5668 * Look for it in a large entry.
5669 */
5670 return szone_size_try_large(szone, ptr);
5671 }
5672
5673 static NOINLINE void *
5674 szone_realloc(szone_t *szone, void *ptr, size_t new_size)
5675 {
5676 size_t old_size;
5677 void *new_ptr;
5678
5679 #if DEBUG_MALLOC
5680 if (LOG(szone, ptr)) {
5681 malloc_printf("in szone_realloc for %p, %d\n", ptr, (unsigned)new_size);
5682 }
5683 #endif
5684 if (!ptr) {
5685 ptr = szone_malloc(szone, new_size);
5686 return ptr;
5687 }
5688 old_size = szone_size(szone, ptr);
5689 if (!old_size) {
5690 szone_error(szone, 1, "pointer being reallocated was not allocated", ptr, NULL);
5691 return NULL;
5692 }
5693 /* we never shrink an allocation */
5694 if (old_size >= new_size)
5695 return ptr;
5696
5697 /*
5698 * If the new size suits the tiny allocator and the pointer being resized
5699 * belongs to a tiny region, try to reallocate in-place.
5700 */
5701 if ((new_size + TINY_QUANTUM - 1) <= (NUM_TINY_SLOTS - 1) * TINY_QUANTUM) {
5702 if (tiny_region_for_ptr_no_lock(szone, ptr) != NULL) {
5703 if (tiny_try_realloc_in_place(szone, ptr, old_size, new_size)) {
5704 return ptr;
5705 }
5706 }
5707
5708 /*
5709 * If the new size suits the small allocator and the pointer being resized
5710 * belongs to a small region, and we're not protecting the small allocations
5711 * try to reallocate in-place.
5712 */
5713 } else if (!((szone->debug_flags & SCALABLE_MALLOC_ADD_GUARD_PAGES) && PROTECT_SMALL) &&
5714 ((new_size + SMALL_QUANTUM - 1) <= szone->large_threshold) &&
5715 (small_region_for_ptr_no_lock(szone, ptr) != NULL)) {
5716 if (small_try_realloc_in_place(szone, ptr, old_size, new_size)) {
5717 return ptr;
5718 }
5719
5720 /*
5721 * If the allocation's a large allocation, try to reallocate in-place there.
5722 */
5723 } else if (!((szone->debug_flags & SCALABLE_MALLOC_ADD_GUARD_PAGES) && PROTECT_SMALL) &&
5724 !(szone->debug_flags & SCALABLE_MALLOC_PURGEABLE) &&
5725 (old_size > szone->large_threshold)) {
5726 if (large_try_realloc_in_place(szone, ptr, old_size, new_size)) {
5727 return ptr;
5728 }
5729 }
5730
5731 /*
5732 * Can't reallocate in place for whatever reason; allocate a new buffer and copy.
5733 */
5734 new_ptr = szone_malloc(szone, new_size);
5735 if (new_ptr == NULL)
5736 return NULL;
5737
5738 /*
5739 * If the allocation's large enough, try to copy using VM. If that fails, or
5740 * if it's too small, just copy by hand.
5741 */
5742 if ((old_size < szone->vm_copy_threshold) ||
5743 vm_copy(mach_task_self(), (vm_address_t)ptr, old_size, (vm_address_t)new_ptr))
5744 memcpy(new_ptr, ptr, old_size);
5745 szone_free(szone, ptr);
5746
5747 #if DEBUG_MALLOC
5748 if (LOG(szone, ptr)) {
5749 malloc_printf("szone_realloc returned %p for %d\n", new_ptr, (unsigned)new_size);
5750 }
5751 #endif
5752 return new_ptr;
5753 }
5754
5755 static NOINLINE void *
5756 szone_memalign(szone_t *szone, size_t alignment, size_t size)
5757 {
5758 if ((size + alignment) < size) // size_t arithmetic wrapped!
5759 return NULL;
5760
5761 // alignment is gauranteed a power of 2 at least as large as sizeof(void *), hence non-zero.
5762 // Since size + alignment didn't wrap, 0 <= size + alignment - 1 < size + alignment
5763 size_t span = size + alignment - 1;
5764
5765 if (alignment <= TINY_QUANTUM) {
5766 return szone_malloc(szone, size); // Trivially satisfied by tiny, small, or large
5767
5768 } else if (span <= (NUM_TINY_SLOTS - 1)*TINY_QUANTUM) {
5769 msize_t mspan = TINY_MSIZE_FOR_BYTES(span + TINY_QUANTUM - 1);
5770 void *p = szone_malloc(szone, span); // avoids inlining tiny_malloc_should_clear(szone, mspan, 0);
5771
5772 if (NULL == p)
5773 return NULL;
5774
5775 size_t offset = ((uintptr_t) p) & (alignment - 1); // p % alignment
5776 size_t pad = (0 == offset) ? 0 : alignment - offset; // p + pad achieves desired alignment
5777
5778 msize_t msize = TINY_MSIZE_FOR_BYTES(size + TINY_QUANTUM - 1);
5779 msize_t mpad = TINY_MSIZE_FOR_BYTES(pad + TINY_QUANTUM - 1);
5780 msize_t mwaste = mspan - msize - mpad; // excess blocks
5781
5782 if (mpad > 0) {
5783 void *q = (void *)(((uintptr_t) p) + pad);
5784
5785 // Mark q as a block header and in-use, thus creating two blocks.
5786 magazine_t *tiny_mag_ptr = mag_lock_zine_for_region_trailer(szone, szone->tiny_magazines,
5787 REGION_TRAILER_FOR_TINY_REGION(TINY_REGION_FOR_PTR(p)),
5788 MAGAZINE_INDEX_FOR_TINY_REGION(TINY_REGION_FOR_PTR(p)));
5789 set_tiny_meta_header_in_use(q, msize);
5790
5791 // set_tiny_meta_header_in_use() "reaffirms" the block_header on the *following* block, so
5792 // now set its in_use bit as well. But only if its within the original allocation made above.
5793 if (mwaste > 0)
5794 BITARRAY_SET(TINY_INUSE_FOR_HEADER(TINY_BLOCK_HEADER_FOR_PTR(q)), TINY_INDEX_FOR_PTR(q) + msize);
5795 SZONE_MAGAZINE_PTR_UNLOCK(szone, tiny_mag_ptr);
5796
5797 // Give up mpad blocks beginning at p to the tiny free list
5798 // region_t r = TINY_REGION_FOR_PTR(p);
5799 szone_free(szone, p); // avoids inlining free_tiny(szone, p, &r);
5800
5801 p = q; // advance p to the desired alignment
5802 }
5803
5804 if (mwaste > 0) {
5805 void *q = (void *)(((uintptr_t) p) + TINY_BYTES_FOR_MSIZE(msize));
5806 // Mark q as block header and in-use, thus creating two blocks.
5807 magazine_t *tiny_mag_ptr = mag_lock_zine_for_region_trailer(szone, szone->tiny_magazines,
5808 REGION_TRAILER_FOR_TINY_REGION(TINY_REGION_FOR_PTR(p)),
5809 MAGAZINE_INDEX_FOR_TINY_REGION(TINY_REGION_FOR_PTR(p)));
5810 set_tiny_meta_header_in_use(q, mwaste);
5811 SZONE_MAGAZINE_PTR_UNLOCK(szone, tiny_mag_ptr);
5812
5813 // Give up mwaste blocks beginning at q to the tiny free list
5814 // region_t r = TINY_REGION_FOR_PTR(q);
5815 szone_free(szone, q); // avoids inlining free_tiny(szone, q, &r);
5816 }
5817
5818 return p; // p has the desired size and alignment, and can later be free()'d
5819
5820 } else if ((NUM_TINY_SLOTS - 1)*TINY_QUANTUM < size && alignment <= SMALL_QUANTUM) {
5821 return szone_malloc(szone, size); // Trivially satisfied by small or large
5822
5823 } else if (!((szone->debug_flags & SCALABLE_MALLOC_ADD_GUARD_PAGES) && PROTECT_SMALL) && (span <= szone->large_threshold)) {
5824
5825 if (size <= (NUM_TINY_SLOTS - 1)*TINY_QUANTUM) {
5826 size = (NUM_TINY_SLOTS - 1)*TINY_QUANTUM + TINY_QUANTUM; // ensure block allocated by small does not have a tiny-possible size
5827 span = size + alignment - 1;
5828 }
5829
5830 msize_t mspan = SMALL_MSIZE_FOR_BYTES(span + SMALL_QUANTUM - 1);
5831 void *p = szone_malloc(szone, span); // avoid inlining small_malloc_should_clear(szone, mspan, 0);
5832
5833 if (NULL == p)
5834 return NULL;
5835
5836 size_t offset = ((uintptr_t) p) & (alignment - 1); // p % alignment
5837 size_t pad = (0 == offset) ? 0 : alignment - offset; // p + pad achieves desired alignment
5838
5839 msize_t msize = SMALL_MSIZE_FOR_BYTES(size + SMALL_QUANTUM - 1);
5840 msize_t mpad = SMALL_MSIZE_FOR_BYTES(pad + SMALL_QUANTUM - 1);
5841 msize_t mwaste = mspan - msize - mpad; // excess blocks
5842
5843 if (mpad > 0) {
5844 void *q = (void *)(((uintptr_t) p) + pad);
5845
5846 // Mark q as block header and in-use, thus creating two blocks.
5847 magazine_t *small_mag_ptr = mag_lock_zine_for_region_trailer(szone, szone->small_magazines,
5848 REGION_TRAILER_FOR_SMALL_REGION(SMALL_REGION_FOR_PTR(p)),
5849 MAGAZINE_INDEX_FOR_SMALL_REGION(SMALL_REGION_FOR_PTR(p)));
5850 small_meta_header_set_in_use(SMALL_META_HEADER_FOR_PTR(p), SMALL_META_INDEX_FOR_PTR(p), mpad);
5851 small_meta_header_set_in_use(SMALL_META_HEADER_FOR_PTR(q), SMALL_META_INDEX_FOR_PTR(q), msize + mwaste);
5852 SZONE_MAGAZINE_PTR_UNLOCK(szone, small_mag_ptr);
5853
5854 // Give up mpad blocks beginning at p to the small free list
5855 // region_t r = SMALL_REGION_FOR_PTR(p);
5856 szone_free(szone, p); // avoid inlining free_small(szone, p, &r);
5857
5858 p = q; // advance p to the desired alignment
5859 }
5860 if (mwaste > 0) {
5861 void *q = (void *)(((uintptr_t) p) + SMALL_BYTES_FOR_MSIZE(msize));
5862 // Mark q as block header and in-use, thus creating two blocks.
5863 magazine_t *small_mag_ptr = mag_lock_zine_for_region_trailer(szone, szone->small_magazines,
5864 REGION_TRAILER_FOR_SMALL_REGION(SMALL_REGION_FOR_PTR(p)),
5865 MAGAZINE_INDEX_FOR_SMALL_REGION(SMALL_REGION_FOR_PTR(p)));
5866 small_meta_header_set_in_use(SMALL_META_HEADER_FOR_PTR(p), SMALL_META_INDEX_FOR_PTR(p), msize);
5867 small_meta_header_set_in_use(SMALL_META_HEADER_FOR_PTR(q), SMALL_META_INDEX_FOR_PTR(q), mwaste);
5868 SZONE_MAGAZINE_PTR_UNLOCK(szone, small_mag_ptr);
5869
5870 // Give up mwaste blocks beginning at q to the small free list
5871 // region_t r = SMALL_REGION_FOR_PTR(q);
5872 szone_free(szone, q); // avoid inlining free_small(szone, q, &r);
5873 }
5874
5875 return p; // p has the desired size and alignment, and can later be free()'d
5876
5877 } else if (szone->large_threshold < size && alignment <= vm_page_size) {
5878 return szone_malloc(szone, size); // Trivially satisfied by large
5879
5880 } else {
5881 // ensure block allocated by large does not have a small-possible size
5882 size_t num_pages = round_page(MAX(szone->large_threshold + 1, size)) >> vm_page_shift;
5883 void *p;
5884
5885 if (num_pages == 0) /* Overflowed */
5886 p = NULL;
5887 else
5888 p = large_malloc(szone, num_pages, MAX(vm_page_shift, __builtin_ctz(alignment)), 0);
5889
5890 return p;
5891 }
5892 /* NOTREACHED */
5893 }
5894
5895 // given a size, returns the number of pointers allocated capable of holding
5896 // that size, up to the limit specified by the 'count' argument. These pointers
5897 // are stored in the 'results' array, which must be allocated by the caller.
5898 // may return zero, since this function is only a best attempt at allocating
5899 // the pointers. clients should be prepared to call malloc for any additional
5900 // blocks they need.
5901 static NOINLINE unsigned
5902 szone_batch_malloc(szone_t *szone, size_t size, void **results, unsigned count)
5903 {
5904 msize_t msize = TINY_MSIZE_FOR_BYTES(size + TINY_QUANTUM - 1);
5905 unsigned found = 0;
5906 mag_index_t mag_index = mag_get_thread_index(szone);
5907 magazine_t *tiny_mag_ptr = &(szone->tiny_magazines[mag_index]);
5908
5909 // only bother implementing this for tiny
5910 if (size > (NUM_TINY_SLOTS - 1)*TINY_QUANTUM)
5911 return 0;
5912 // make sure to return objects at least one quantum in size
5913 if (!msize)
5914 msize = 1;
5915
5916 CHECK(szone, __PRETTY_FUNCTION__);
5917
5918 // We must lock the zone now, since tiny_malloc_from_free_list assumes that
5919 // the caller has done so.
5920 SZONE_MAGAZINE_PTR_LOCK(szone, tiny_mag_ptr);
5921
5922 // with the zone locked, allocate objects from the free list until all
5923 // sufficiently large objects have been exhausted, or we have met our quota
5924 // of objects to allocate.
5925 while (found < count) {
5926 void *ptr = tiny_malloc_from_free_list(szone, tiny_mag_ptr, mag_index, msize);
5927 if (!ptr)
5928 break;
5929
5930 *results++ = ptr;
5931 found++;
5932 }
5933 SZONE_MAGAZINE_PTR_UNLOCK(szone, tiny_mag_ptr);
5934 return found;
5935 }
5936
5937 /* Try caching the tiny_region and checking if the next ptr hits there. */
5938 static NOINLINE void
5939 szone_batch_free(szone_t *szone, void **to_be_freed, unsigned count)
5940 {
5941 unsigned cc = 0;
5942 void *ptr;
5943 region_t tiny_region = NULL;
5944 boolean_t is_free;
5945 msize_t msize;
5946 magazine_t *tiny_mag_ptr = NULL;
5947 mag_index_t mag_index = -1;
5948
5949 // frees all the pointers in to_be_freed
5950 // note that to_be_freed may be overwritten during the process
5951 if (!count)
5952 return;
5953
5954 CHECK(szone, __PRETTY_FUNCTION__);
5955 while (cc < count) {
5956 ptr = to_be_freed[cc];
5957 if (ptr) {
5958 if (NULL == tiny_region || tiny_region != TINY_REGION_FOR_PTR(ptr)) { // region same as last iteration?
5959 if (tiny_mag_ptr) { // non-NULL iff magazine lock taken
5960 SZONE_MAGAZINE_PTR_UNLOCK(szone, tiny_mag_ptr);
5961 tiny_mag_ptr = NULL;
5962 }
5963
5964 tiny_region = tiny_region_for_ptr_no_lock(szone, ptr);
5965
5966 if (tiny_region) {
5967 tiny_mag_ptr = mag_lock_zine_for_region_trailer(szone, szone->tiny_magazines,
5968 REGION_TRAILER_FOR_TINY_REGION(tiny_region),
5969 MAGAZINE_INDEX_FOR_TINY_REGION(tiny_region));
5970 mag_index = MAGAZINE_INDEX_FOR_TINY_REGION(tiny_region);
5971 }
5972 }
5973 if (tiny_region) {
5974 // this is a tiny pointer
5975 if (TINY_INDEX_FOR_PTR(ptr) >= NUM_TINY_BLOCKS)
5976 break; // pointer to metadata; let the standard free deal with it
5977 msize = get_tiny_meta_header(ptr, &is_free);
5978 if (is_free)
5979 break; // a double free; let the standard free deal with it
5980
5981 tiny_free_no_lock(szone, tiny_mag_ptr, mag_index, tiny_region, ptr, msize);
5982 to_be_freed[cc] = NULL;
5983 } else {
5984 // No region in this zone claims ptr; let the standard free deal with it
5985 break;
5986 }
5987 }
5988 cc++;
5989 }
5990
5991 if (tiny_mag_ptr) {
5992 SZONE_MAGAZINE_PTR_UNLOCK(szone, tiny_mag_ptr);
5993 tiny_mag_ptr = NULL;
5994 }
5995
5996 CHECK(szone, __PRETTY_FUNCTION__);
5997 while (count--) {
5998 ptr = to_be_freed[count];
5999 if (ptr)
6000 szone_free(szone, ptr);
6001 }
6002 }
6003
6004 // FIXME: Suppose one of the locks is held?
6005 static void
6006 szone_destroy(szone_t *szone)
6007 {
6008 size_t index;
6009 large_entry_t *large;
6010 vm_range_t range_to_deallocate;
6011
6012 /* destroy large entries */
6013 index = szone->num_large_entries;
6014 while (index--) {
6015 large = szone->large_entries + index;
6016 if (large->address) {
6017 // we deallocate_pages, including guard pages
6018 deallocate_pages(szone, (void *)(large->address), large->size, szone->debug_flags);
6019 }
6020 }
6021 large_entries_free_no_lock(szone, szone->large_entries, szone->num_large_entries, &range_to_deallocate);
6022 if (range_to_deallocate.size)
6023 deallocate_pages(szone, (void *)range_to_deallocate.address, (size_t)range_to_deallocate.size, 0);
6024
6025 /* destroy tiny regions */
6026 for (index = 0; index < szone->tiny_region_generation->num_regions_allocated; ++index)
6027 if ((HASHRING_OPEN_ENTRY != szone->tiny_region_generation->hashed_regions[index]) &&
6028 (HASHRING_REGION_DEALLOCATED != szone->tiny_region_generation->hashed_regions[index]))
6029 deallocate_pages(szone, szone->tiny_region_generation->hashed_regions[index], TINY_REGION_SIZE, 0);
6030
6031 /* destroy small regions */
6032 for (index = 0; index < szone->small_region_generation->num_regions_allocated; ++index)
6033 if ((HASHRING_OPEN_ENTRY != szone->small_region_generation->hashed_regions[index]) &&
6034 (HASHRING_REGION_DEALLOCATED != szone->small_region_generation->hashed_regions[index]))
6035 deallocate_pages(szone, szone->small_region_generation->hashed_regions[index], SMALL_REGION_SIZE, 0);
6036
6037 /* destroy region hash rings, if any */
6038 if (szone->tiny_region_generation->hashed_regions != szone->initial_tiny_regions) {
6039 size_t size = round_page(szone->tiny_region_generation->num_regions_allocated * sizeof(region_t));
6040 deallocate_pages(szone, szone->tiny_region_generation->hashed_regions, size, 0);
6041 }
6042 if (szone->small_region_generation->hashed_regions != szone->initial_small_regions) {
6043 size_t size = round_page(szone->small_region_generation->num_regions_allocated * sizeof(region_t));
6044 deallocate_pages(szone, szone->small_region_generation->hashed_regions, size, 0);
6045 }
6046
6047 /* Now destroy the separate szone region */
6048 if (szone->cpu_id_key != (pthread_key_t) -1)
6049 (void)pthread_key_delete(szone->cpu_id_key);
6050 deallocate_pages(szone, (void *)&(szone->tiny_magazines[-1]), TINY_MAGAZINE_PAGED_SIZE, SCALABLE_MALLOC_ADD_GUARD_PAGES);
6051 deallocate_pages(szone, (void *)&(szone->small_magazines[-1]), SMALL_MAGAZINE_PAGED_SIZE, SCALABLE_MALLOC_ADD_GUARD_PAGES);
6052 deallocate_pages(szone, (void *)szone, SZONE_PAGED_SIZE, SCALABLE_MALLOC_ADD_GUARD_PAGES);
6053 }
6054
6055 static NOINLINE size_t
6056 szone_good_size(szone_t *szone, size_t size)
6057 {
6058 msize_t msize;
6059 int guard_small = (szone->debug_flags & SCALABLE_MALLOC_ADD_GUARD_PAGES) && PROTECT_SMALL;
6060
6061 // Find a good size for this tiny allocation.
6062 if (size <= (NUM_TINY_SLOTS - 1) * TINY_QUANTUM) {
6063 msize = TINY_MSIZE_FOR_BYTES(size + TINY_QUANTUM - 1);
6064 if (!msize)
6065 msize = 1;
6066 return TINY_BYTES_FOR_MSIZE(msize);
6067 }
6068
6069 // Find a good size for this small allocation.
6070 if (!guard_small && (size <= szone->large_threshold)) {
6071 msize = SMALL_MSIZE_FOR_BYTES(size + SMALL_QUANTUM - 1);
6072 if (!msize)
6073 msize = 1;
6074 return SMALL_BYTES_FOR_MSIZE(msize);
6075 }
6076
6077 // Check for integer overflow on the size, since unlike the two cases above,
6078 // there is no upper bound on allocation size at this point.
6079 if (size > round_page(size))
6080 return (size_t)(-1LL);
6081
6082 #if DEBUG_MALLOC
6083 // It is not acceptable to see a size of zero here, since that means we
6084 // failed to catch a request for zero bytes in the tiny check, or the size
6085 // overflowed to zero during some arithmetic.
6086 if (size == 0)
6087 malloc_printf("szone_good_size() invariant broken %y\n", size);
6088 #endif
6089 return round_page(size);
6090 }
6091
6092 unsigned szone_check_counter = 0;
6093 unsigned szone_check_start = 0;
6094 unsigned szone_check_modulo = 1;
6095
6096 static NOINLINE boolean_t
6097 szone_check_all(szone_t *szone, const char *function)
6098 {
6099 size_t index;
6100
6101 /* check tiny regions - chould check region count */
6102 for (index = 0; index < szone->tiny_region_generation->num_regions_allocated; ++index) {
6103 region_t tiny = szone->tiny_region_generation->hashed_regions[index];
6104
6105 if (HASHRING_REGION_DEALLOCATED == tiny)
6106 continue;
6107
6108 if (tiny) {
6109 magazine_t *tiny_mag_ptr = mag_lock_zine_for_region_trailer(szone, szone->tiny_magazines,
6110 REGION_TRAILER_FOR_TINY_REGION(tiny), MAGAZINE_INDEX_FOR_TINY_REGION(tiny));
6111
6112 if (!tiny_check_region(szone, tiny)) {
6113 SZONE_MAGAZINE_PTR_UNLOCK(szone, tiny_mag_ptr);
6114 szone->debug_flags &= ~ CHECK_REGIONS;
6115 szone_error(szone, 1, "check: tiny region incorrect", NULL,
6116 "*** tiny region %ld incorrect szone_check_all(%s) counter=%d\n",
6117 index, function, szone_check_counter);
6118 return 0;
6119 }
6120 SZONE_MAGAZINE_PTR_UNLOCK(szone, tiny_mag_ptr);
6121 }
6122 }
6123 /* check tiny free lists */
6124 for (index = 0; index < NUM_TINY_SLOTS; ++index) {
6125 if (!tiny_free_list_check(szone, index)) {
6126 szone->debug_flags &= ~ CHECK_REGIONS;
6127 szone_error(szone, 1, "check: tiny free list incorrect", NULL,
6128 "*** tiny free list incorrect (slot=%ld) szone_check_all(%s) counter=%d\n",
6129 index, function, szone_check_counter);
6130 return 0;
6131 }
6132 }
6133
6134 /* check small regions - could check region count */
6135 for (index = 0; index < szone->small_region_generation->num_regions_allocated; ++index) {
6136 region_t small = szone->small_region_generation->hashed_regions[index];
6137
6138 if (HASHRING_REGION_DEALLOCATED == small)
6139 continue;
6140
6141 if (small) {
6142 magazine_t *small_mag_ptr = mag_lock_zine_for_region_trailer(szone, szone->small_magazines,
6143 REGION_TRAILER_FOR_SMALL_REGION(small), MAGAZINE_INDEX_FOR_SMALL_REGION(small));
6144
6145 if (!small_check_region(szone, small)) {
6146 SZONE_MAGAZINE_PTR_UNLOCK(szone, small_mag_ptr);
6147 szone->debug_flags &= ~ CHECK_REGIONS;
6148 szone_error(szone, 1, "check: small region incorrect", NULL,
6149 "*** small region %ld incorrect szone_check_all(%s) counter=%d\n",
6150 index, function, szone_check_counter);
6151 return 0;
6152 }
6153 SZONE_MAGAZINE_PTR_UNLOCK(szone, small_mag_ptr);
6154 }
6155 }
6156 /* check small free lists */
6157 for (index = 0; index < szone->num_small_slots; ++index) {
6158 if (!small_free_list_check(szone, index)) {
6159 szone->debug_flags &= ~ CHECK_REGIONS;
6160 szone_error(szone, 1, "check: small free list incorrect", NULL,
6161 "*** small free list incorrect (slot=%ld) szone_check_all(%s) counter=%d\n",
6162 index, function, szone_check_counter);
6163 return 0;
6164 }
6165 }
6166
6167 return 1;
6168 }
6169
6170 static boolean_t
6171 szone_check(szone_t *szone)
6172 {
6173 if ((++szone_check_counter % 10000) == 0)
6174 _malloc_printf(ASL_LEVEL_NOTICE, "at szone_check counter=%d\n", szone_check_counter);
6175
6176 if (szone_check_counter < szone_check_start)
6177 return 1;
6178
6179 if (szone_check_counter % szone_check_modulo)
6180 return 1;
6181
6182 return szone_check_all(szone, "");
6183 }
6184
6185 static kern_return_t
6186 szone_ptr_in_use_enumerator(task_t task, void *context, unsigned type_mask, vm_address_t zone_address,
6187 memory_reader_t reader, vm_range_recorder_t recorder)
6188 {
6189 szone_t *szone;
6190 kern_return_t err;
6191
6192 if (!reader) reader = _szone_default_reader;
6193
6194 err = reader(task, zone_address, sizeof(szone_t), (void **)&szone);
6195 if (err) return err;
6196
6197 err = tiny_in_use_enumerator(task, context, type_mask, szone, reader, recorder);
6198 if (err) return err;
6199
6200 err = small_in_use_enumerator(task, context, type_mask, szone, reader, recorder);
6201 if (err) return err;
6202
6203 err = large_in_use_enumerator(task, context, type_mask,
6204 (vm_address_t)szone->large_entries, szone->num_large_entries, reader, recorder);
6205 return err;
6206 }
6207
6208 // Following method is deprecated: use scalable_zone_statistics instead
6209 void
6210 scalable_zone_info(malloc_zone_t *zone, unsigned *info_to_fill, unsigned count)
6211 {
6212 szone_t *szone = (void *)zone;
6213 unsigned info[13];
6214
6215 // We do not lock to facilitate debug
6216
6217 size_t s = 0;
6218 unsigned t = 0;
6219 size_t u = 0;
6220 mag_index_t mag_index;
6221
6222 for (mag_index = -1; mag_index < szone->num_tiny_magazines; mag_index++) {
6223 s += szone->tiny_magazines[mag_index].mag_bytes_free_at_end;
6224 t += szone->tiny_magazines[mag_index].mag_num_objects;
6225 u += szone->tiny_magazines[mag_index].mag_num_bytes_in_objects;
6226 }
6227
6228 info[4] = t;
6229 info[5] = u;
6230
6231 for (t = 0, u = 0, mag_index = -1; mag_index < szone->num_small_magazines; mag_index++) {
6232 s += szone->small_magazines[mag_index].mag_bytes_free_at_end;
6233 t += szone->small_magazines[mag_index].mag_num_objects;
6234 u += szone->small_magazines[mag_index].mag_num_bytes_in_objects;
6235 }
6236
6237 info[6] = t;
6238 info[7] = u;
6239
6240 info[8] = szone->num_large_objects_in_use;
6241 info[9] = szone->num_bytes_in_large_objects;
6242
6243 info[10] = 0; // DEPRECATED szone->num_huge_entries;
6244 info[11] = 0; // DEPRECATED szone->num_bytes_in_huge_objects;
6245
6246 info[12] = szone->debug_flags;
6247
6248 info[0] = info[4] + info[6] + info[8] + info[10];
6249 info[1] = info[5] + info[7] + info[9] + info[11];
6250
6251 info[3] = (szone->num_tiny_regions - szone->num_tiny_regions_dealloc) * TINY_REGION_SIZE +
6252 (szone->num_small_regions - szone->num_small_regions_dealloc) * SMALL_REGION_SIZE + info[9] + info[11];
6253
6254 info[2] = info[3] - s;
6255 memcpy(info_to_fill, info, sizeof(unsigned)*count);
6256 }
6257
6258 // FIXME: consistent picture requires locking!
6259 static NOINLINE void
6260 szone_print(szone_t *szone, boolean_t verbose)
6261 {
6262 unsigned info[13];
6263 size_t index;
6264 region_t region;
6265
6266 scalable_zone_info((void *)szone, info, 13);
6267 _malloc_printf(MALLOC_PRINTF_NOLOG | MALLOC_PRINTF_NOPREFIX,
6268 "Scalable zone %p: inUse=%d(%y) touched=%y allocated=%y flags=%d\n",
6269 szone, info[0], info[1], info[2], info[3], info[12]);
6270 _malloc_printf(MALLOC_PRINTF_NOLOG | MALLOC_PRINTF_NOPREFIX,
6271 "\ttiny=%d(%y) small=%d(%y) large=%d(%y) huge=%d(%y)\n",
6272 info[4], info[5], info[6], info[7], info[8], info[9], info[10], info[11]);
6273 // tiny
6274 _malloc_printf(MALLOC_PRINTF_NOLOG | MALLOC_PRINTF_NOPREFIX,
6275 "%d tiny regions:\n", szone->num_tiny_regions);
6276 if (szone->num_tiny_regions_dealloc)
6277 _malloc_printf(MALLOC_PRINTF_NOLOG | MALLOC_PRINTF_NOPREFIX,
6278 "[%d tiny regions have been vm_deallocate'd]\n", szone->num_tiny_regions_dealloc);
6279 for (index = 0; index < szone->tiny_region_generation->num_regions_allocated; ++index) {
6280 region = szone->tiny_region_generation->hashed_regions[index];
6281 if (HASHRING_OPEN_ENTRY != region && HASHRING_REGION_DEALLOCATED != region) {
6282 mag_index_t mag_index = MAGAZINE_INDEX_FOR_TINY_REGION(region);
6283 print_tiny_region(verbose, region, (region == szone->tiny_magazines[mag_index].mag_last_region) ?
6284 szone->tiny_magazines[mag_index].mag_bytes_free_at_end : 0);
6285 }
6286 }
6287 if (verbose)
6288 print_tiny_free_list(szone);
6289 // small
6290 _malloc_printf(MALLOC_PRINTF_NOLOG | MALLOC_PRINTF_NOPREFIX,
6291 "%d small regions:\n", szone->num_small_regions);
6292 if (szone->num_small_regions_dealloc)
6293 _malloc_printf(MALLOC_PRINTF_NOLOG | MALLOC_PRINTF_NOPREFIX,
6294 "[%d small regions have been vm_deallocate'd]\n", szone->num_small_regions_dealloc);
6295 for (index = 0; index < szone->small_region_generation->num_regions_allocated; ++index) {
6296 region = szone->small_region_generation->hashed_regions[index];
6297 if (HASHRING_OPEN_ENTRY != region && HASHRING_REGION_DEALLOCATED != region) {
6298 mag_index_t mag_index = MAGAZINE_INDEX_FOR_SMALL_REGION(region);
6299 print_small_region(szone, verbose, region,
6300 (region == szone->small_magazines[mag_index].mag_last_region) ?
6301 szone->small_magazines[mag_index].mag_bytes_free_at_end : 0);
6302 }
6303 }
6304 if (verbose)
6305 print_small_free_list(szone);
6306 }
6307
6308 static void
6309 szone_log(malloc_zone_t *zone, void *log_address)
6310 {
6311 szone_t *szone = (szone_t *)zone;
6312
6313 szone->log_address = log_address;
6314 }
6315
6316 static void
6317 szone_force_lock(szone_t *szone)
6318 {
6319 mag_index_t i;
6320
6321 for (i = 0; i < szone->num_tiny_magazines; ++i) {
6322 SZONE_MAGAZINE_PTR_LOCK(szone, (&(szone->tiny_magazines[i])));
6323 }
6324 SZONE_MAGAZINE_PTR_LOCK(szone, (&(szone->tiny_magazines[DEPOT_MAGAZINE_INDEX])));
6325
6326 for (i = 0; i < szone->num_small_magazines; ++i) {
6327 SZONE_MAGAZINE_PTR_LOCK(szone, (&(szone->small_magazines[i])));
6328 }
6329 SZONE_MAGAZINE_PTR_LOCK(szone, (&(szone->small_magazines[DEPOT_MAGAZINE_INDEX])));
6330
6331 SZONE_LOCK(szone);
6332 }
6333
6334 static void
6335 szone_force_unlock(szone_t *szone)
6336 {
6337 mag_index_t i;
6338
6339 SZONE_UNLOCK(szone);
6340
6341 for (i = -1; i < szone->num_small_magazines; ++i) {
6342 SZONE_MAGAZINE_PTR_UNLOCK(szone, (&(szone->small_magazines[i])));
6343 }
6344
6345 for (i = -1; i < szone->num_tiny_magazines; ++i) {
6346 SZONE_MAGAZINE_PTR_UNLOCK(szone, (&(szone->tiny_magazines[i])));
6347 }
6348 }
6349
6350 static boolean_t
6351 szone_locked(szone_t *szone)
6352 {
6353 mag_index_t i;
6354 int tookLock;
6355
6356 tookLock = SZONE_TRY_LOCK(szone);
6357 if (tookLock == 0)
6358 return 1;
6359 SZONE_UNLOCK(szone);
6360
6361 for (i = -1; i < szone->num_small_magazines; ++i) {
6362 tookLock = SZONE_MAGAZINE_PTR_TRY_LOCK(szone, (&(szone->small_magazines[i])));
6363 if (tookLock == 0)
6364 return 1;
6365 SZONE_MAGAZINE_PTR_UNLOCK(szone, (&(szone->small_magazines[i])));
6366 }
6367
6368 for (i = -1; i < szone->num_tiny_magazines; ++i) {
6369 tookLock = SZONE_MAGAZINE_PTR_TRY_LOCK(szone, (&(szone->tiny_magazines[i])));
6370 if (tookLock == 0)
6371 return 1;
6372 SZONE_MAGAZINE_PTR_UNLOCK(szone, (&(szone->tiny_magazines[i])));
6373 }
6374 return 0;
6375 }
6376
6377 boolean_t
6378 scalable_zone_statistics(malloc_zone_t *zone, malloc_statistics_t *stats, unsigned subzone)
6379 {
6380 szone_t *szone = (szone_t *)zone;
6381
6382 switch (subzone) {
6383 case 0:
6384 {
6385 size_t s = 0;
6386 unsigned t = 0;
6387 size_t u = 0;
6388 mag_index_t mag_index;
6389
6390 for (mag_index = -1; mag_index < szone->num_tiny_magazines; mag_index++) {
6391 s += szone->tiny_magazines[mag_index].mag_bytes_free_at_end;
6392 t += szone->tiny_magazines[mag_index].mag_num_objects;
6393 u += szone->tiny_magazines[mag_index].mag_num_bytes_in_objects;
6394 }
6395
6396 stats->blocks_in_use = t;
6397 stats->size_in_use = u;
6398 stats->size_allocated = (szone->num_tiny_regions - szone->num_tiny_regions_dealloc) * TINY_REGION_SIZE;
6399 stats->max_size_in_use = stats->size_allocated - s;
6400 return 1;
6401 }
6402 case 1:
6403 {
6404 size_t s = 0;
6405 unsigned t = 0;
6406 size_t u = 0;
6407 mag_index_t mag_index;
6408
6409 for (mag_index = -1; mag_index < szone->num_small_magazines; mag_index++) {
6410 s += szone->small_magazines[mag_index].mag_bytes_free_at_end;
6411 t += szone->small_magazines[mag_index].mag_num_objects;
6412 u += szone->small_magazines[mag_index].mag_num_bytes_in_objects;
6413 }
6414
6415 stats->blocks_in_use = t;
6416 stats->size_in_use = u;
6417 stats->size_allocated = (szone->num_small_regions - szone->num_small_regions_dealloc) * SMALL_REGION_SIZE;
6418 stats->max_size_in_use = stats->size_allocated - s;
6419 return 1;
6420 }
6421 case 2:
6422 stats->blocks_in_use = szone->num_large_objects_in_use;
6423 stats->size_in_use = szone->num_bytes_in_large_objects;
6424 stats->max_size_in_use = stats->size_allocated = stats->size_in_use;
6425 return 1;
6426 case 3:
6427 stats->blocks_in_use = 0; // DEPRECATED szone->num_huge_entries;
6428 stats->size_in_use = 0; // DEPRECATED szone->num_bytes_in_huge_objects;
6429 stats->max_size_in_use = stats->size_allocated = 0;
6430 return 1;
6431 }
6432 return 0;
6433 }
6434
6435 static void
6436 szone_statistics(szone_t *szone, malloc_statistics_t *stats)
6437 {
6438 size_t large;
6439
6440 size_t s = 0;
6441 unsigned t = 0;
6442 size_t u = 0;
6443 mag_index_t mag_index;
6444
6445 for (mag_index = -1; mag_index < szone->num_tiny_magazines; mag_index++) {
6446 s += szone->tiny_magazines[mag_index].mag_bytes_free_at_end;
6447 t += szone->tiny_magazines[mag_index].mag_num_objects;
6448 u += szone->tiny_magazines[mag_index].mag_num_bytes_in_objects;
6449 }
6450
6451 for (mag_index = -1; mag_index < szone->num_small_magazines; mag_index++) {
6452 s += szone->small_magazines[mag_index].mag_bytes_free_at_end;
6453 t += szone->small_magazines[mag_index].mag_num_objects;
6454 u += szone->small_magazines[mag_index].mag_num_bytes_in_objects;
6455 }
6456
6457 large = szone->num_bytes_in_large_objects + 0; // DEPRECATED szone->num_bytes_in_huge_objects;
6458
6459 stats->blocks_in_use = t + szone->num_large_objects_in_use + 0; // DEPRECATED szone->num_huge_entries;
6460 stats->size_in_use = u + large;
6461 stats->max_size_in_use = stats->size_allocated =
6462 (szone->num_tiny_regions - szone->num_tiny_regions_dealloc) * TINY_REGION_SIZE +
6463 (szone->num_small_regions - szone->num_small_regions_dealloc) * SMALL_REGION_SIZE + large;
6464 // Now we account for the untouched areas
6465 stats->max_size_in_use -= s;
6466 }
6467
6468 static void *
6469 legacy_zeroing_large_malloc(szone_t *szone, size_t size) {
6470 if (size > LARGE_THRESHOLD) // Leopard and earlier returned a ZFOD range, so ...
6471 return szone_calloc(szone, 1, size); // Clear to zero always, ham-handedly touching in each page
6472 else
6473 return szone_malloc(szone, size);
6474 }
6475
6476 static void *
6477 legacy_zeroing_large_valloc(szone_t *szone, size_t size) {
6478 void *p = szone_valloc(szone, size);
6479
6480 // Leopard and earlier returned a ZFOD range, so ...
6481 memset(p, 0, size); // Clear to zero always, ham-handedly touching in each page
6482 return p;
6483 }
6484
6485 void zeroify_scalable_zone(malloc_zone_t *zone)
6486 {
6487 szone_t *szone = (szone_t *)zone;
6488
6489 if (szone) {
6490 szone->basic_zone.malloc = (void *)legacy_zeroing_large_malloc;
6491 szone->basic_zone.valloc = (void *)legacy_zeroing_large_valloc;
6492 }
6493 }
6494
6495 static const struct malloc_introspection_t szone_introspect = {
6496 (void *)szone_ptr_in_use_enumerator,
6497 (void *)szone_good_size,
6498 (void *)szone_check,
6499 (void *)szone_print,
6500 szone_log,
6501 (void *)szone_force_lock,
6502 (void *)szone_force_unlock,
6503 (void *)szone_statistics,
6504 (void *)szone_locked,
6505 }; // marked as const to spare the DATA section
6506
6507 malloc_zone_t *
6508 create_scalable_zone(size_t initial_size, unsigned debug_flags)
6509 {
6510 szone_t *szone;
6511 uint64_t hw_memsize = 0;
6512 size_t uint64_t_size = sizeof(hw_memsize);
6513 int err;
6514
6515 /*
6516 * Sanity-check our build-time assumptions about the size of a page.
6517 * Since we have sized various things assuming the default page size,
6518 * attempting to determine it dynamically is not useful.
6519 */
6520 if ((vm_page_size != _vm_page_size) || (vm_page_shift != _vm_page_shift)) {
6521 malloc_printf("*** FATAL ERROR - machine page size does not match our assumptions.\n");
6522 exit(-1);
6523 }
6524
6525 #if defined(__i386__) || defined(__x86_64__)
6526 if (_COMM_PAGE_VERSION_REQD > (*((short *) _COMM_PAGE_VERSION))) { // _COMM_PAGE_CPU_NUMBER must be present at runtime
6527 malloc_printf("*** ERROR - comm page version mismatch.\n");
6528 exit(-1);
6529 }
6530 #endif
6531
6532 /* get memory for the zone, which is now separate from any region.
6533 add guard pages to prevent walking from any other vm allocations
6534 to here and overwriting the function pointers in basic_zone. */
6535 szone = allocate_pages(NULL, SZONE_PAGED_SIZE, 0, SCALABLE_MALLOC_ADD_GUARD_PAGES, VM_MEMORY_MALLOC);
6536 if (!szone)
6537 return NULL;
6538
6539 /* set up the szone structure */
6540 #if 0
6541 #warning CHECK_REGIONS enabled
6542 debug_flags |= CHECK_REGIONS;
6543 #endif
6544 #if 0
6545 #warning LOG enabled
6546 szone->log_address = ~0;
6547 #endif
6548 szone->trg[0].nextgen = &(szone->trg[1]);
6549 szone->trg[1].nextgen = &(szone->trg[0]);
6550 szone->tiny_region_generation = &(szone->trg[0]);
6551
6552 szone->tiny_region_generation->hashed_regions = szone->initial_tiny_regions;
6553 szone->tiny_region_generation->num_regions_allocated = INITIAL_NUM_REGIONS;
6554 szone->tiny_region_generation->num_regions_allocated_shift = INITIAL_NUM_REGIONS_SHIFT;
6555
6556 szone->srg[0].nextgen = &(szone->srg[1]);
6557 szone->srg[1].nextgen = &(szone->srg[0]);
6558 szone->small_region_generation = &(szone->srg[0]);
6559
6560 szone->small_region_generation->hashed_regions = szone->initial_small_regions;
6561 szone->small_region_generation->num_regions_allocated = INITIAL_NUM_REGIONS;
6562 szone->small_region_generation->num_regions_allocated_shift = INITIAL_NUM_REGIONS_SHIFT;
6563
6564
6565 /*
6566 * Initialize variables that size the free list for SMALL allocations based
6567 * upon the amount of memory in the system. Switch to a larger number of
6568 * free list entries at 1GB.
6569 */
6570 if (0 == sysctlbyname("hw.memsize", &hw_memsize, &uint64_t_size, 0, 0) &&
6571 hw_memsize >= (1ULL << 30)) {
6572 szone->is_largemem = 1;
6573 szone->num_small_slots = NUM_SMALL_SLOTS_LARGEMEM;
6574 szone->large_threshold = LARGE_THRESHOLD_LARGEMEM;
6575 szone->vm_copy_threshold = VM_COPY_THRESHOLD_LARGEMEM;
6576 } else {
6577 szone->is_largemem = 0;
6578 szone->num_small_slots = NUM_SMALL_SLOTS;
6579 szone->large_threshold = LARGE_THRESHOLD;
6580 szone->vm_copy_threshold = VM_COPY_THRESHOLD;
6581 }
6582 #if LARGE_CACHE
6583 szone->large_entry_cache_hoard_lmit = hw_memsize >> 10; // madvise(..., MADV_REUSABLE) death-row arrivals above this threshold [~0.1%]
6584
6585 /* <rdar://problem/6610904> Reset protection when returning a previous large allocation? */
6586 int32_t libSystemVersion = NSVersionOfLinkTimeLibrary("System");
6587 if ((-1 != libSystemVersion) && ((libSystemVersion >> 16) < 112) /* CFSystemVersionSnowLeopard */)
6588 szone->large_legacy_reset_mprotect = TRUE;
6589 else
6590 szone->large_legacy_reset_mprotect = FALSE;
6591 #endif
6592
6593 // Initialize the security token.
6594 #if __LP64__
6595 szone->cookie = ((uintptr_t)arc4random() << 32) | (uintptr_t)arc4random();
6596 #else
6597 szone->cookie = arc4random();
6598 #endif
6599
6600 szone->basic_zone.version = 6;
6601 szone->basic_zone.size = (void *)szone_size;
6602 szone->basic_zone.malloc = (void *)szone_malloc;
6603 szone->basic_zone.calloc = (void *)szone_calloc;
6604 szone->basic_zone.valloc = (void *)szone_valloc;
6605 szone->basic_zone.free = (void *)szone_free;
6606 szone->basic_zone.realloc = (void *)szone_realloc;
6607 szone->basic_zone.destroy = (void *)szone_destroy;
6608 szone->basic_zone.batch_malloc = (void *)szone_batch_malloc;
6609 szone->basic_zone.batch_free = (void *)szone_batch_free;
6610 szone->basic_zone.introspect = (struct malloc_introspection_t *)&szone_introspect;
6611 szone->basic_zone.memalign = (void *)szone_memalign;
6612 szone->basic_zone.free_definite_size = (void *)szone_free_definite_size;
6613 szone->debug_flags = debug_flags;
6614 LOCK_INIT(szone->large_szone_lock);
6615
6616 #if defined(__ppc__) || defined(__ppc64__)
6617 /*
6618 * In the interest of compatibility for PPC applications executing via Rosetta,
6619 * arrange to zero-fill allocations as occurred by side effect in Leopard and earlier.
6620 */
6621 zeroify_scalable_zone((malloc_zone_t *)szone);
6622 #endif
6623
6624 if ((err = pthread_key_create(&(szone->cpu_id_key), NULL))) {
6625 malloc_printf("*** ERROR -pthread_key_create failure err=%d.\n", err);
6626 szone->cpu_id_key = (pthread_key_t) -1;
6627 }
6628
6629 // Query the number of configured processors.
6630 // Uniprocessor case gets just one tiny and one small magazine (whose index is zero). This gives
6631 // the same behavior as the original scalable malloc. MP gets per-CPU magazines
6632 // that scale (way) better.
6633 int nproc = sysconf(_SC_NPROCESSORS_CONF);
6634 szone->num_tiny_magazines = (nproc > 1) ? MIN(nproc, TINY_MAX_MAGAZINES) : 1;
6635
6636 // FIXME vm_allocate() based on number of configured CPUs
6637 magazine_t *tiny_magazines = allocate_pages(NULL, TINY_MAGAZINE_PAGED_SIZE, 0,
6638 SCALABLE_MALLOC_ADD_GUARD_PAGES, VM_MEMORY_MALLOC);
6639 if (NULL == tiny_magazines)
6640 return NULL;
6641
6642 szone->tiny_magazines = &(tiny_magazines[1]); // szone->tiny_magazines[-1] is the Depot
6643
6644 // The magazines are indexed in [0 .. (num_tiny_magazines - 1)]
6645 // Find the smallest power of 2 that exceeds (num_tiny_magazines - 1)
6646 szone->num_tiny_magazines_mask_shift = 0;
6647 int i = 1;
6648 while( i <= (szone->num_tiny_magazines - 1) ) {
6649 szone->num_tiny_magazines_mask_shift++;
6650 i <<= 1;
6651 }
6652
6653 // Now if i <= TINY_MAX_MAGAZINES we'll never access tiny_magazines[] out of bounds.
6654 if (i > TINY_MAX_MAGAZINES) {
6655 malloc_printf("*** FATAL ERROR - magazine mask exceeds allocated magazines.\n");
6656 exit(-1);
6657 }
6658
6659 // Reduce i by 1 to obtain a mask covering [0 .. (num_tiny_magazines - 1)]
6660 szone->num_tiny_magazines_mask = i - 1; // A mask used for hashing to a magazine index (and a safety aid)
6661
6662 // Init the tiny_magazine locks
6663 LOCK_INIT(szone->tiny_regions_lock);
6664 LOCK_INIT(szone->tiny_magazines[DEPOT_MAGAZINE_INDEX].magazine_lock);
6665 for (i = 0; i < szone->num_tiny_magazines; ++i) {
6666 LOCK_INIT(szone->tiny_magazines[i].magazine_lock);
6667 }
6668
6669 szone->num_small_magazines = (nproc > 1) ? MIN(nproc, SMALL_MAX_MAGAZINES) : 1;
6670
6671 // FIXME vm_allocate() based on number of configured CPUs
6672 magazine_t *small_magazines = allocate_pages(NULL, SMALL_MAGAZINE_PAGED_SIZE, 0,
6673 SCALABLE_MALLOC_ADD_GUARD_PAGES, VM_MEMORY_MALLOC);
6674 if (NULL == small_magazines)
6675 return NULL;
6676
6677 szone->small_magazines = &(small_magazines[1]); // szone->small_magazines[-1] is the Depot
6678
6679 // The magazines are indexed in [0 .. (num_small_magazines - 1)]
6680 // Find the smallest power of 2 that exceeds (num_small_magazines - 1)
6681 szone->num_small_magazines_mask_shift = 0;
6682 while( i <= (szone->num_small_magazines - 1) ) {
6683 szone->num_small_magazines_mask_shift++;
6684 i <<= 1;
6685 }
6686
6687 // Now if i <= SMALL_MAX_MAGAZINES we'll never access small_magazines[] out of bounds.
6688 if (i > SMALL_MAX_MAGAZINES) {
6689 malloc_printf("*** FATAL ERROR - magazine mask exceeds allocated magazines.\n");
6690 exit(-1);
6691 }
6692
6693 // Reduce i by 1 to obtain a mask covering [0 .. (num_small_magazines - 1)]
6694 szone->num_small_magazines_mask = i - 1; // A mask used for hashing to a magazine index (and a safety aid)
6695
6696 // Init the small_magazine locks
6697 LOCK_INIT(szone->small_regions_lock);
6698 LOCK_INIT(szone->small_magazines[DEPOT_MAGAZINE_INDEX].magazine_lock);
6699 for (i = 0; i < szone->num_small_magazines; ++i) {
6700 LOCK_INIT(szone->small_magazines[i].magazine_lock);
6701 }
6702
6703 CHECK(szone, __PRETTY_FUNCTION__);
6704 return (malloc_zone_t *)szone;
6705 }
6706
6707 //
6708 // purgeable zones have their own "large" allocation pool, but share "tiny" and "large"
6709 // heaps with a helper_zone identified in the call to create_purgeable_zone()
6710 //
6711 static size_t
6712 purgeable_size(szone_t *szone, const void *ptr)
6713 {
6714 size_t t = szone_size_try_large(szone, ptr);
6715
6716 if (t)
6717 return t;
6718 else
6719 return szone_size(szone->helper_zone, ptr);
6720 }
6721
6722 static void *
6723 purgeable_malloc(szone_t *szone, size_t size) {
6724 if (size <= szone->large_threshold)
6725 return szone_malloc(szone->helper_zone, size);
6726 else
6727 return szone_malloc(szone, size);
6728 }
6729
6730 static void *
6731 purgeable_calloc(szone_t *szone, size_t num_items, size_t size)
6732 {
6733 size_t total_bytes = num_items * size;
6734
6735 // Check for overflow of integer multiplication
6736 if (num_items > 1) {
6737 #if __LP64__ /* size_t is uint64_t */
6738 if ((num_items | size) & 0xffffffff00000000ul) {
6739 // num_items or size equals or exceeds sqrt(2^64) == 2^32, appeal to wider arithmetic
6740 __uint128_t product = ((__uint128_t)num_items) * ((__uint128_t)size);
6741 if ((uint64_t)(product >> 64)) // compiles to test on upper register of register pair
6742 return NULL;
6743 }
6744 #else /* size_t is uint32_t */
6745 if ((num_items | size) & 0xffff0000ul) {
6746 // num_items or size equals or exceeds sqrt(2^32) == 2^16, appeal to wider arithmetic
6747 uint64_t product = ((uint64_t)num_items) * ((uint64_t)size);
6748 if ((uint32_t)(product >> 32)) // compiles to test on upper register of register pair
6749 return NULL;
6750 }
6751 #endif
6752 }
6753
6754 if (total_bytes <= szone->large_threshold)
6755 return szone_calloc(szone->helper_zone, 1, total_bytes);
6756 else
6757 return szone_calloc(szone, 1, total_bytes);
6758 }
6759
6760 static void *
6761 purgeable_valloc(szone_t *szone, size_t size)
6762 {
6763 if (size <= szone->large_threshold)
6764 return szone_valloc(szone->helper_zone, size);
6765 else
6766 return szone_valloc(szone, size);
6767 }
6768
6769 static void
6770 purgeable_free(szone_t *szone, void *ptr)
6771 {
6772 large_entry_t *entry;
6773
6774 SZONE_LOCK(szone);
6775 entry = large_entry_for_pointer_no_lock(szone, ptr);
6776 SZONE_UNLOCK(szone);
6777 if (entry) {
6778 return free_large(szone, ptr);
6779 } else {
6780 return szone_free(szone->helper_zone, ptr);
6781 }
6782 }
6783
6784 static void
6785 purgeable_free_definite_size(szone_t *szone, void *ptr, size_t size)
6786 {
6787 if (size <= szone->large_threshold)
6788 return szone_free_definite_size(szone->helper_zone, ptr, size);
6789 else
6790 return szone_free_definite_size(szone, ptr, size);
6791 }
6792
6793 static void *
6794 purgeable_realloc(szone_t *szone, void *ptr, size_t new_size)
6795 {
6796 if (new_size <= szone->large_threshold)
6797 return szone_realloc(szone->helper_zone, ptr, new_size);
6798 else
6799 return szone_realloc(szone, ptr, new_size);
6800 }
6801
6802 static void
6803 purgeable_destroy(szone_t *szone)
6804 {
6805 /* destroy large entries */
6806 size_t index = szone->num_large_entries;
6807 large_entry_t *large;
6808 vm_range_t range_to_deallocate;
6809
6810 while (index--) {
6811 large = szone->large_entries + index;
6812 if (large->address) {
6813 // we deallocate_pages, including guard pages
6814 deallocate_pages(szone, (void *)(large->address), large->size, szone->debug_flags);
6815 }
6816 }
6817 large_entries_free_no_lock(szone, szone->large_entries, szone->num_large_entries, &range_to_deallocate);
6818 if (range_to_deallocate.size)
6819 deallocate_pages(szone, (void *)range_to_deallocate.address, (size_t)range_to_deallocate.size, 0);
6820
6821 /* Now destroy the separate szone region */
6822 deallocate_pages(szone, (void *)szone, SZONE_PAGED_SIZE, SCALABLE_MALLOC_ADD_GUARD_PAGES);
6823 }
6824
6825 static unsigned
6826 purgeable_batch_malloc(szone_t *szone, size_t size, void **results, unsigned count)
6827 {
6828 return szone_batch_malloc(szone->helper_zone, size, results, count);
6829 }
6830
6831 static void
6832 purgeable_batch_free(szone_t *szone, void **to_be_freed, unsigned count)
6833 {
6834 return szone_batch_free(szone->helper_zone, to_be_freed, count);
6835 }
6836
6837 static void *
6838 purgeable_memalign(szone_t *szone, size_t alignment, size_t size)
6839 {
6840 if (size <= szone->large_threshold)
6841 return szone_memalign(szone->helper_zone, alignment, size);
6842 else
6843 return szone_memalign(szone, alignment, size);
6844 }
6845
6846 static kern_return_t
6847 purgeable_ptr_in_use_enumerator(task_t task, void *context, unsigned type_mask, vm_address_t zone_address,
6848 memory_reader_t reader, vm_range_recorder_t recorder)
6849 {
6850 szone_t *szone;
6851 kern_return_t err;
6852
6853 if (!reader) reader = _szone_default_reader;
6854
6855 err = reader(task, zone_address, sizeof(szone_t), (void **)&szone);
6856 if (err) return err;
6857
6858 err = large_in_use_enumerator(task, context, type_mask,
6859 (vm_address_t)szone->large_entries, szone->num_large_entries, reader, recorder);
6860 return err;
6861 }
6862
6863 static size_t
6864 purgeable_good_size(szone_t *szone, size_t size)
6865 {
6866 if (size <= szone->large_threshold)
6867 return szone_good_size(szone->helper_zone, size);
6868 else
6869 return szone_good_size(szone, size);
6870 }
6871
6872 static boolean_t
6873 purgeable_check(szone_t *szone)
6874 {
6875 return 1;
6876 }
6877
6878 static void
6879 purgeable_print(szone_t *szone, boolean_t verbose)
6880 {
6881 _malloc_printf(MALLOC_PRINTF_NOLOG | MALLOC_PRINTF_NOPREFIX,
6882 "Scalable zone %p: inUse=%d(%y) flags=%d\n",
6883 szone, szone->num_large_objects_in_use, szone->num_bytes_in_large_objects, szone->debug_flags);
6884 }
6885
6886 static void
6887 purgeable_log(malloc_zone_t *zone, void *log_address)
6888 {
6889 szone_t *szone = (szone_t *)zone;
6890
6891 szone->log_address = log_address;
6892 }
6893
6894 static void
6895 purgeable_force_lock(szone_t *szone)
6896 {
6897 SZONE_LOCK(szone);
6898 }
6899
6900 static void
6901 purgeable_force_unlock(szone_t *szone)
6902 {
6903 SZONE_UNLOCK(szone);
6904 }
6905
6906 static void
6907 purgeable_statistics(szone_t *szone, malloc_statistics_t *stats)
6908 {
6909 stats->blocks_in_use = szone->num_large_objects_in_use;
6910 stats->size_in_use = stats->max_size_in_use = stats->size_allocated = szone->num_bytes_in_large_objects;
6911 }
6912
6913 static boolean_t
6914 purgeable_locked(szone_t *szone)
6915 {
6916 int tookLock;
6917
6918 tookLock = SZONE_TRY_LOCK(szone);
6919 if (tookLock == 0)
6920 return 1;
6921 SZONE_UNLOCK(szone);
6922 return 0;
6923 }
6924
6925 static const struct malloc_introspection_t purgeable_introspect = {
6926 (void *)purgeable_ptr_in_use_enumerator,
6927 (void *)purgeable_good_size,
6928 (void *)purgeable_check,
6929 (void *)purgeable_print,
6930 purgeable_log,
6931 (void *)purgeable_force_lock,
6932 (void *)purgeable_force_unlock,
6933 (void *)purgeable_statistics,
6934 (void *)purgeable_locked,
6935 }; // marked as const to spare the DATA section
6936
6937 malloc_zone_t *
6938 create_purgeable_zone(size_t initial_size, malloc_zone_t *malloc_default_zone, unsigned debug_flags)
6939 {
6940 szone_t *szone;
6941
6942 /* get memory for the zone, which is now separate from any region.
6943 add guard pages to prevent walking from any other vm allocations
6944 to here and overwriting the function pointers in basic_zone. */
6945 szone = allocate_pages(NULL, SZONE_PAGED_SIZE, 0, SCALABLE_MALLOC_ADD_GUARD_PAGES, VM_MEMORY_MALLOC);
6946 if (!szone)
6947 return NULL;
6948
6949 /* set up the szone structure */
6950 #if 0
6951 #warning LOG enabled
6952 szone->log_address = ~0;
6953 #endif
6954
6955 /* Purgeable zone does not participate in the adaptive "largemem" sizing. */
6956 szone->is_largemem = 0;
6957 szone->large_threshold = LARGE_THRESHOLD;
6958 szone->vm_copy_threshold = VM_COPY_THRESHOLD;
6959
6960 #if LARGE_CACHE
6961 /* <rdar://problem/6610904> Reset protection when returning a previous large allocation? */
6962 int32_t libSystemVersion = NSVersionOfLinkTimeLibrary("System");
6963 if ((-1 != libSystemVersion) && ((libSystemVersion >> 16) < 112) /* CFSystemVersionSnowLeopard */)
6964 szone->large_legacy_reset_mprotect = TRUE;
6965 else
6966 szone->large_legacy_reset_mprotect = FALSE;
6967 #endif
6968
6969 szone->basic_zone.version = 6;
6970 szone->basic_zone.size = (void *)purgeable_size;
6971 szone->basic_zone.malloc = (void *)purgeable_malloc;
6972 szone->basic_zone.calloc = (void *)purgeable_calloc;
6973 szone->basic_zone.valloc = (void *)purgeable_valloc;
6974 szone->basic_zone.free = (void *)purgeable_free;
6975 szone->basic_zone.realloc = (void *)purgeable_realloc;
6976 szone->basic_zone.destroy = (void *)purgeable_destroy;
6977 szone->basic_zone.batch_malloc = (void *)purgeable_batch_malloc;
6978 szone->basic_zone.batch_free = (void *)purgeable_batch_free;
6979 szone->basic_zone.introspect = (struct malloc_introspection_t *)&purgeable_introspect;
6980 szone->basic_zone.memalign = (void *)purgeable_memalign;
6981 szone->basic_zone.free_definite_size = (void *)purgeable_free_definite_size;
6982
6983 szone->debug_flags = debug_flags | SCALABLE_MALLOC_PURGEABLE;
6984
6985 /* Purgeable zone does not support SCALABLE_MALLOC_ADD_GUARD_PAGES. */
6986 if (szone->debug_flags & SCALABLE_MALLOC_ADD_GUARD_PAGES) {
6987 _malloc_printf(ASL_LEVEL_INFO, "purgeable zone does not support guard pages\n");
6988 szone->debug_flags &= ~SCALABLE_MALLOC_ADD_GUARD_PAGES;
6989 }
6990
6991 LOCK_INIT(szone->large_szone_lock);
6992
6993 szone->helper_zone = (struct szone_s *)malloc_default_zone;
6994
6995 CHECK(szone, __PRETTY_FUNCTION__);
6996 return (malloc_zone_t *)szone;
6997 }
6998
6999 /*
7000 * For use by CheckFix: create a new zone whose behavior is, apart from
7001 * the use of death-row and per-CPU magazines, that of Leopard.
7002 */
7003 static NOINLINE void *
7004 legacy_valloc(szone_t *szone, size_t size)
7005 {
7006 void *ptr;
7007 size_t num_pages;
7008
7009 num_pages = round_page(size) >> vm_page_shift;
7010 ptr = large_malloc(szone, num_pages, 0, TRUE);
7011 #if DEBUG_MALLOC
7012 if (LOG(szone, ptr))
7013 malloc_printf("legacy_valloc returned %p\n", ptr);
7014 #endif
7015 return ptr;
7016 }
7017
7018 malloc_zone_t *
7019 create_legacy_scalable_zone(size_t initial_size, unsigned debug_flags)
7020 {
7021 malloc_zone_t *mzone = create_scalable_zone(initial_size, debug_flags);
7022 szone_t *szone = (szone_t *)mzone;
7023
7024 if (!szone)
7025 return NULL;
7026
7027 szone->is_largemem = 0;
7028 szone->num_small_slots = NUM_SMALL_SLOTS;
7029 szone->large_threshold = LARGE_THRESHOLD;
7030 szone->vm_copy_threshold = VM_COPY_THRESHOLD;
7031
7032 szone->basic_zone.valloc = (void *)legacy_valloc;
7033 szone->basic_zone.free_definite_size = NULL;
7034
7035 return mzone;
7036 }
7037
7038 /********* Support code for emacs unexec ************/
7039
7040 /* History of freezedry version numbers:
7041 *
7042 * 1) Old malloc (before the scalable malloc implementation in this file
7043 * existed).
7044 * 2) Original freezedrying code for scalable malloc. This code was apparently
7045 * based on the old freezedrying code and was fundamentally flawed in its
7046 * assumption that tracking allocated memory regions was adequate to fake
7047 * operations on freezedried memory. This doesn't work, since scalable
7048 * malloc does not store flags in front of large page-aligned allocations.
7049 * 3) Original szone-based freezedrying code.
7050 * 4) Fresher malloc with tiny zone
7051 * 5) 32/64bit compatible malloc
7052 * 6) Metadata within 1MB and 8MB region for tiny and small
7053 *
7054 * No version backward compatibility is provided, but the version number does
7055 * make it possible for malloc_jumpstart() to return an error if the application
7056 * was freezedried with an older version of malloc.
7057 */
7058 #define MALLOC_FREEZEDRY_VERSION 6
7059
7060 typedef struct {
7061 unsigned version;
7062 unsigned nszones;
7063 szone_t *szones;
7064 } malloc_frozen;
7065
7066 static void *
7067 frozen_malloc(szone_t *zone, size_t new_size)
7068 {
7069 return malloc(new_size);
7070 }
7071
7072 static void *
7073 frozen_calloc(szone_t *zone, size_t num_items, size_t size)
7074 {
7075 return calloc(num_items, size);
7076 }
7077
7078 static void *
7079 frozen_valloc(szone_t *zone, size_t new_size)
7080 {
7081 return valloc(new_size);
7082 }
7083
7084 static void *
7085 frozen_realloc(szone_t *zone, void *ptr, size_t new_size)
7086 {
7087 size_t old_size = szone_size(zone, ptr);
7088 void *new_ptr;
7089
7090 if (new_size <= old_size) {
7091 return ptr;
7092 }
7093 new_ptr = malloc(new_size);
7094 if (old_size > 0) {
7095 memcpy(new_ptr, ptr, old_size);
7096 }
7097 return new_ptr;
7098 }
7099
7100 static void
7101 frozen_free(szone_t *zone, void *ptr)
7102 {
7103 }
7104
7105 static void
7106 frozen_destroy(szone_t *zone)
7107 {
7108 }
7109
7110 /********* Pseudo-private API for emacs unexec ************/
7111
7112 /*
7113 * malloc_freezedry() records all of the szones in use, so that they can be
7114 * partially reconstituted by malloc_jumpstart(). Due to the differences
7115 * between reconstituted memory regions and those created by the szone code,
7116 * care is taken not to reallocate from the freezedried memory, except in the
7117 * case of a non-growing realloc().
7118 *
7119 * Due to the flexibility provided by the zone registration mechanism, it is
7120 * impossible to implement generic freezedrying for any zone type. This code
7121 * only handles applications that use the szone allocator, so malloc_freezedry()
7122 * returns 0 (error) if any non-szone zones are encountered.
7123 */
7124
7125 uintptr_t
7126 malloc_freezedry(void)
7127 {
7128 extern unsigned malloc_num_zones;
7129 extern malloc_zone_t **malloc_zones;
7130 malloc_frozen *data;
7131 unsigned i;
7132
7133 /* Allocate space in which to store the freezedry state. */
7134 data = (malloc_frozen *) malloc(sizeof(malloc_frozen));
7135
7136 /* Set freezedry version number so that malloc_jumpstart() can check for compatibility. */
7137 data->version = MALLOC_FREEZEDRY_VERSION;
7138
7139 /* Allocate the array of szone pointers. */
7140 data->nszones = malloc_num_zones;
7141 data->szones = (szone_t *) calloc(malloc_num_zones, sizeof(szone_t));
7142
7143 /*
7144 * Fill in the array of szone structures. They are copied rather than
7145 * referenced, since the originals are likely to be clobbered during malloc
7146 * initialization.
7147 */
7148 for (i = 0; i < malloc_num_zones; i++) {
7149 if (strcmp(malloc_zones[i]->zone_name, "DefaultMallocZone")) {
7150 /* Unknown zone type. */
7151 free(data->szones);
7152 free(data);
7153 return 0;
7154 }
7155 memcpy(&data->szones[i], malloc_zones[i], sizeof(szone_t));
7156 }
7157
7158 return((uintptr_t)data);
7159 }
7160
7161 int
7162 malloc_jumpstart(uintptr_t cookie)
7163 {
7164 malloc_frozen *data = (malloc_frozen *)cookie;
7165 unsigned i;
7166
7167 if (data->version != MALLOC_FREEZEDRY_VERSION) {
7168 /* Unsupported freezedry version. */
7169 return 1;
7170 }
7171
7172 for (i = 0; i < data->nszones; i++) {
7173 /* Set function pointers. Even the functions that stay the same must be
7174 * set, since there are no guarantees that they will be mapped to the
7175 * same addresses. */
7176 data->szones[i].basic_zone.size = (void *) szone_size;
7177 data->szones[i].basic_zone.malloc = (void *) frozen_malloc;
7178 data->szones[i].basic_zone.calloc = (void *) frozen_calloc;
7179 data->szones[i].basic_zone.valloc = (void *) frozen_valloc;
7180 data->szones[i].basic_zone.free = (void *) frozen_free;
7181 data->szones[i].basic_zone.realloc = (void *) frozen_realloc;
7182 data->szones[i].basic_zone.destroy = (void *) frozen_destroy;
7183 data->szones[i].basic_zone.introspect = (struct malloc_introspection_t *)&szone_introspect;
7184
7185 /* Register the freezedried zone. */
7186 malloc_zone_register(&data->szones[i].basic_zone);
7187 }
7188
7189 return 0;
7190 }