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