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