]> git.saurik.com Git - apple/libc.git/blob - gen.subproj/scalable_malloc.c
a19c7a4506a834fecf17ed94d5b9316b55efbdd7
[apple/libc.git] / gen.subproj / scalable_malloc.c
1 /*
2 * Copyright (c) 1999 Apple Computer, Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * The contents of this file constitute Original Code as defined in and
7 * are subject to the Apple Public Source License Version 1.1 (the
8 * "License"). You may not use this file except in compliance with the
9 * License. Please obtain a copy of the License at
10 * http://www.apple.com/publicsource and read it before using this file.
11 *
12 * This Original Code and all software distributed under the License are
13 * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER
14 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
15 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the
17 * License for the specific language governing rights and limitations
18 * under the License.
19 *
20 * @APPLE_LICENSE_HEADER_END@
21 */
22
23 /* Author: Bertrand Serlet, August 1999 */
24
25 #import "scalable_malloc.h"
26
27 #define __POSIX_LIB__
28 #import <unistd.h>
29 #import <pthread_internals.h> // for spin lock
30 #import <libc.h>
31 #include <mach/vm_statistics.h>
32
33 /********************* DEFINITIONS ************************/
34
35 static unsigned vm_page_shift = 0; // guaranteed to be intialized by zone creation
36
37 #define DEBUG_MALLOC 0 // set to one to debug malloc itself
38 #define DEBUG_CLIENT 0 // set to one to help debug a nasty memory smasher
39
40 #if DEBUG_MALLOC
41 #warning DEBUG ENABLED
42 #define INLINE
43 #else
44 #define INLINE inline
45 #endif
46
47 #define CHECK_REGIONS (1 << 31)
48
49 #define VM_COPY_THRESHOLD (40 * 1024)
50 // When all memory is touched after a copy, vm_copy() is always a lose
51 // But if the memory is only read, vm_copy() wins over memmove() at 3 or 4 pages (on a G3/300MHz)
52 #define KILL_THRESHOLD (32 * 1024)
53
54 #define LARGE_THRESHOLD (3 * vm_page_size) // at or above this use "large"
55
56 #define SHIFT_QUANTUM 4 // Required for AltiVec
57 #define QUANTUM (1 << SHIFT_QUANTUM) // allocation quantum
58 #define MIN_BLOCK 1 // minimum size, in QUANTUM multiples
59
60 /* The header of a small block in use contains its size (expressed as multiples of QUANTUM, and header included), or 0;
61 If 0 then the block is either free (in which case the size is directly at the block itself), or the last block (indicated by either being beyond range, or having 0 in the block itself) */
62
63 #define PTR_HEADER_SIZE (sizeof(msize_t))
64 #define FOLLOWING_PTR(ptr,msize) (((char *)(ptr)) + ((msize) << SHIFT_QUANTUM))
65 #define PREVIOUS_MSIZE(ptr) ((msize_t *)(ptr))[-2]
66
67 #define THIS_FREE 0x8000 // indicates this block is free
68 #define PREV_FREE 0x4000 // indicates previous block is free
69 #define MSIZE_FLAGS_FOR_PTR(ptr) (((msize_t *)(ptr))[-1])
70
71 #define REGION_SIZE (1 << (16 - 2 + SHIFT_QUANTUM)) // since we only have 16 bits for msize_t, and 1 bit is taken by THIS_FREE, and 1 by PREV_FREE
72
73 #define INITIAL_NUM_REGIONS 8 // must always be at least 2 to always have 1 slot empty
74
75 #define CHECKSUM_MAGIC 0x357B
76
77 #define PROTECT_SMALL 0 // Should be 0: 1 is too slow for normal use
78
79 #define LARGE_CACHE_SIZE 1 // define hysterisis of large chunks
80 #define MAX_LARGE_SIZE_TO_CACHE (128*1024) /* blocks larger than this are not cached */
81
82 #define MAX_RECORDER_BUFFER 256
83
84 #define MAX_GRAIN 64
85
86 typedef unsigned short msize_t; // a size in multiples of SHIFT_QUANTUM
87
88 typedef struct {
89 unsigned checksum;
90 void *previous;
91 void *next;
92 } free_list_t;
93
94 typedef struct {
95 unsigned address_and_num_pages;
96 // this type represents both an address and a number of pages
97 // the low bits are the number of pages
98 // the high bits are the address
99 // note that the exact number of bits used for depends on the page size
100 // also, this cannot represent pointers larger than 1 << (vm_page_shift * 2)
101 } compact_range_t;
102
103 typedef vm_address_t region_t;
104
105 typedef compact_range_t large_entry_t;
106
107 typedef vm_range_t huge_entry_t;
108
109 typedef unsigned short grain_t;
110
111 typedef struct {
112 malloc_zone_t basic_zone;
113 pthread_lock_t lock;
114 unsigned debug_flags;
115 void *log_address;
116
117 /* Regions for small objects */
118 unsigned num_regions;
119 region_t *regions;
120 // this array is always created with 1 extra slot to be able to add a region without taking memory right away
121 unsigned last_region_hit;
122 free_list_t *free_list[MAX_GRAIN];
123 unsigned num_bytes_free_in_last_region; // these bytes are cleared
124 unsigned num_small_objects;
125 unsigned num_bytes_in_small_objects;
126
127 /* Cache of recently freed large object */
128 vm_range_t large_to_deallocate[LARGE_CACHE_SIZE];
129 // Entries that are 0 should be discarded
130
131 /* large objects: vm_page_shift <= log2(size) < 2 *vm_page_shift */
132 unsigned num_large_objects_in_use; // does not count the large entries in large_to_deallocate
133 unsigned num_large_entries;
134 unsigned num_bytes_in_large_objects;
135 large_entry_t *large_entries;
136 // large_entries are hashed by location
137 // large_entries that are 0 should be discarded
138
139 /* huge objects: log2(size) >= 2 *vm_page_shift */
140 unsigned num_bytes_in_huge_objects;
141 unsigned num_huge_entries;
142 huge_entry_t *huge_entries;
143 } szone_t;
144
145 static void *szone_malloc(szone_t *szone, size_t size);
146 static void *szone_valloc(szone_t *szone, size_t size);
147 static INLINE void *szone_malloc_should_clear(szone_t *szone, size_t size, boolean_t cleared_requested);
148 static void szone_free(szone_t *szone, void *ptr);
149 static size_t szone_good_size(szone_t *szone, size_t size);
150 static boolean_t szone_check_all(szone_t *szone, const char *function);
151 static void szone_print(szone_t *szone, boolean_t verbose);
152 static INLINE region_t *region_for_ptr_no_lock(szone_t *szone, const void *ptr);
153 static vm_range_t large_free_no_lock(szone_t *szone, large_entry_t *entry);
154
155 #define LOG(szone,ptr) (szone->log_address && (szone->num_small_objects > 8) && (((unsigned)szone->log_address == -1) || (szone->log_address == (void *)(ptr))))
156
157 /********************* ACCESSOR MACROS ************************/
158
159 #define SZONE_LOCK(szone) LOCK(szone->lock)
160 #define SZONE_UNLOCK(szone) UNLOCK(szone->lock)
161
162 #define CHECK(szone,fun) if ((szone)->debug_flags & CHECK_REGIONS) szone_check_all(szone, fun)
163
164 #define REGION_ADDRESS(region) (region)
165 #define REGION_END(region) (region+REGION_SIZE)
166
167 #define LARGE_ENTRY_ADDRESS(entry) (((entry).address_and_num_pages >> vm_page_shift) << vm_page_shift)
168 #define LARGE_ENTRY_NUM_PAGES(entry) ((entry).address_and_num_pages & ((1 << vm_page_shift) - 1))
169 #define LARGE_ENTRY_SIZE(entry) (LARGE_ENTRY_NUM_PAGES(entry) << vm_page_shift)
170 #define LARGE_ENTRY_MATCHES(entry,ptr) (!(((entry).address_and_num_pages - (unsigned)(ptr)) >> vm_page_shift))
171 #define LARGE_ENTRY_IS_EMPTY(entry) (!((entry).address_and_num_pages))
172
173 /********************* VERY LOW LEVEL UTILITIES ************************/
174
175 static void szone_error(szone_t *szone, const char *msg, const void *ptr) {
176 if (szone) SZONE_UNLOCK(szone);
177 if (ptr) {
178 malloc_printf("*** malloc[%d]: error for object %p: %s\n", getpid(), ptr, msg);
179 #if DEBUG_MALLOC
180 szone_print(szone, 1);
181 #endif
182 } else {
183 malloc_printf("*** malloc[%d]: error: %s\n", getpid(), msg);
184 }
185 #if DEBUG_CLIENT
186 malloc_printf("*** Sleeping to help debug\n");
187 sleep(3600); // to help debug
188 #endif
189 }
190
191 static void protect(szone_t *szone, vm_address_t address, vm_size_t size, unsigned protection, unsigned debug_flags) {
192 kern_return_t err;
193 if (!(debug_flags & SCALABLE_MALLOC_DONT_PROTECT_PRELUDE)) {
194 err = vm_protect(mach_task_self(), address - vm_page_size, vm_page_size, 0, protection);
195 if (err) malloc_printf("*** malloc[%d]: Can't protect(%x) region for prelude guard page at 0x%x\n", getpid(), protection, address - vm_page_size);
196 }
197 if (!(debug_flags & SCALABLE_MALLOC_DONT_PROTECT_POSTLUDE)) {
198 err = vm_protect(mach_task_self(), (vm_address_t)(address + size), vm_page_size, 0, protection);
199 if (err) malloc_printf("*** malloc[%d]: Can't protect(%x) region for postlude guard page at 0x%x\n", getpid(), protection, address + size);
200 }
201 }
202
203 static vm_address_t allocate_pages(szone_t *szone, size_t size, unsigned debug_flags, int vm_page_label) {
204 kern_return_t err;
205 vm_address_t addr;
206 boolean_t add_guard_pages = debug_flags & SCALABLE_MALLOC_ADD_GUARD_PAGES;
207 size_t allocation_size = round_page(size);
208 if (!allocation_size) allocation_size = vm_page_size;
209 if (add_guard_pages) allocation_size += 2 * vm_page_size;
210 err = vm_allocate(mach_task_self(), &addr, allocation_size, vm_page_label | 1);
211 if (err) {
212 szone_error(szone, "Can't allocate region", NULL);
213 return NULL;
214 }
215 if (add_guard_pages) {
216 addr += vm_page_size;
217 protect(szone, addr, size, 0, debug_flags);
218 }
219 return addr;
220 }
221
222 static void deallocate_pages(szone_t *szone, vm_address_t addr, size_t size, unsigned debug_flags) {
223 kern_return_t err;
224 boolean_t add_guard_pages = debug_flags & SCALABLE_MALLOC_ADD_GUARD_PAGES;
225 if (add_guard_pages) {
226 addr -= vm_page_size;
227 size += 2 * vm_page_size;
228 }
229 err = vm_deallocate(mach_task_self(), addr, size);
230 if (err) {
231 szone_error(szone, "Can't deallocate_pages region", (void *)addr);
232 }
233 }
234
235 static kern_return_t _szone_default_reader(task_t task, vm_address_t address, vm_size_t size, void **ptr) {
236 *ptr = (void *)address;
237 return 0;
238 }
239
240 /********************* RANGE UTILITIES ************************/
241
242 static const vm_range_t zero_range = {0, 0};
243
244 static vm_range_t coalesce_range(vm_range_t *ranges, unsigned count, vm_range_t range) {
245 // Given a sequence of ranges and a range, tries to find an abutting range
246 // If no, returns original range
247 // Else zeroes out coalesced range, and reapplies with coalesced range
248 unsigned index = count;
249 vm_range_t *current = ranges;
250 while (index--) {
251 vm_range_t this = *current++;
252 if (!this.size) continue;
253 if (this.address + this.size == range.address) {
254 range.address = this.address;
255 range.size += this.size;
256 current[-1] = zero_range;
257 return coalesce_range(ranges, count, range);
258 }
259 if (range.address + range.size == this.address) {
260 range.size += this.size;
261 current[-1] = zero_range;
262 return coalesce_range(ranges, count, range);
263 }
264 }
265 return range;
266 }
267
268 static INLINE vm_range_t *first_zero_range(vm_range_t *ranges, unsigned count) {
269 // Given a sequence of ranges, find the first empty slot
270 // or returns NULL
271 while (count--) {
272 if (!ranges->size) return ranges;
273 ranges++;
274 }
275 return NULL;
276 }
277
278 static vm_range_t *largest_range(vm_range_t *ranges, unsigned count) {
279 // Given a sequence of ranges, find the largest range
280 // Returns NULL on empty arrays
281 vm_range_t *largest_range;
282 if (!count) return NULL;
283 largest_range = ranges;
284 count--; ranges++;
285 while (count--) {
286 if (ranges->size > largest_range->size) largest_range = ranges;
287 ranges++;
288 }
289 return largest_range;
290 }
291
292 static vm_range_t *first_range_greater_or_equal(vm_range_t *ranges, unsigned count, vm_size_t size) {
293 // Given a sequence of ranges, find the first range greater than range
294 // Returns NULL when none found
295 while (count--) {
296 if (ranges->size >= size) return ranges;
297 ranges++;
298 }
299 return NULL;
300 }
301
302 /********************* FREE LIST UTILITIES ************************/
303
304 static INLINE grain_t grain_for_msize(szone_t *szone, msize_t msize) {
305 // assumes msize >= MIN_BLOCK
306 #if DEBUG_MALLOC
307 if (msize < MIN_BLOCK) {
308 szone_error(szone, "grain_for_msize: msize too small", NULL);
309 }
310 #endif
311 return (msize < MAX_GRAIN + MIN_BLOCK) ? msize - MIN_BLOCK : MAX_GRAIN - 1;
312 }
313
314 static INLINE msize_t msize_for_grain(szone_t *szone, grain_t grain) {
315 // 0 if multiple sizes
316 return (grain < MAX_GRAIN - 1) ? grain + MIN_BLOCK : 0;
317 }
318
319 static INLINE void free_list_checksum(szone_t *szone, free_list_t *ptr) {
320 // We always checksum, as testing whether to do it (based on szone->debug_flags) is as fast as doing it
321 if (ptr->checksum != (((unsigned)ptr->previous) ^ ((unsigned)ptr->next) ^ CHECKSUM_MAGIC)) {
322 szone_error(szone, "Incorrect check sum for freed object - object was probably modified after beeing freed; break at szone_error", ptr);
323 }
324 }
325
326 static INLINE void free_list_set_checksum(szone_t *szone, free_list_t *ptr) {
327 // We always set checksum, as testing whether to do it (based on szone->debug_flags) is slower than just doing it
328 ptr->checksum = ((unsigned)ptr->previous) ^ ((unsigned)ptr->next) ^ CHECKSUM_MAGIC;
329 }
330
331 static void free_list_add_ptr(szone_t *szone, void *ptr, msize_t msize) {
332 // Adds an item to the proper free list
333 // Also marks the header of the block properly
334 grain_t grain = grain_for_msize(szone, msize);
335 free_list_t *free_ptr = ptr;
336 free_list_t *free_head = szone->free_list[grain];
337 msize_t *follower = (msize_t *)FOLLOWING_PTR(ptr, msize);
338 #if DEBUG_MALLOC
339 if (LOG(szone,ptr)) malloc_printf("In free_list_add_ptr(), ptr=%p, msize=%d\n", ptr, msize);
340 if (((unsigned)ptr) & (QUANTUM - 1)) {
341 szone_error(szone, "free_list_add_ptr: Unaligned ptr", ptr);
342 }
343 #endif
344 MSIZE_FLAGS_FOR_PTR(ptr) = msize | THIS_FREE;
345 if (free_head) {
346 free_list_checksum(szone, free_head);
347 #if DEBUG_MALLOC
348 if (free_head->previous) {
349 malloc_printf("ptr=%p grain=%d free_head=%p previous=%p\n", ptr, grain, free_head, free_head->previous);
350 szone_error(szone, "free_list_add_ptr: Internal invariant broken (free_head->previous)", ptr);
351 }
352 if (!(MSIZE_FLAGS_FOR_PTR(free_head) & THIS_FREE)) {
353 malloc_printf("ptr=%p grain=%d free_head=%p\n", ptr, grain, free_head);
354 szone_error(szone, "free_list_add_ptr: Internal invariant broken (free_head is not a free pointer)", ptr);
355 }
356 if ((grain != MAX_GRAIN-1) && (MSIZE_FLAGS_FOR_PTR(free_head) != (THIS_FREE | msize))) {
357 malloc_printf("ptr=%p grain=%d free_head=%p previous_msize=%d\n", ptr, grain, free_head, MSIZE_FLAGS_FOR_PTR(free_head));
358 szone_error(szone, "free_list_add_ptr: Internal invariant broken (incorrect msize)", ptr);
359 }
360 #endif
361 free_head->previous = free_ptr;
362 free_list_set_checksum(szone, free_head);
363 }
364 free_ptr->previous = NULL;
365 free_ptr->next = free_head;
366 free_list_set_checksum(szone, free_ptr);
367 szone->free_list[grain] = free_ptr;
368 // mark the end of this block
369 PREVIOUS_MSIZE(follower) = msize;
370 MSIZE_FLAGS_FOR_PTR(follower) |= PREV_FREE;
371 }
372
373 static void free_list_remove_ptr(szone_t *szone, void *ptr, msize_t msize) {
374 // Removes item in the proper free list
375 // msize could be read, but all callers have it so we pass it in
376 grain_t grain = grain_for_msize(szone, msize);
377 free_list_t *free_ptr = ptr;
378 free_list_t *next = free_ptr->next;
379 free_list_t *previous = free_ptr->previous;
380 #if DEBUG_MALLOC
381 if (LOG(szone,ptr)) malloc_printf("In free_list_remove_ptr(), ptr=%p, msize=%d\n", ptr, msize);
382 #endif
383 free_list_checksum(szone, free_ptr);
384 if (!previous) {
385 #if DEBUG_MALLOC
386 if (szone->free_list[grain] != ptr) {
387 malloc_printf("ptr=%p grain=%d msize=%d szone->free_list[grain]=%p\n", ptr, grain, msize, szone->free_list[grain]);
388 szone_error(szone, "free_list_remove_ptr: Internal invariant broken (szone->free_list[grain])", ptr);
389 return;
390 }
391 #endif
392 szone->free_list[grain] = next;
393 } else {
394 previous->next = next;
395 free_list_set_checksum(szone, previous);
396 }
397 if (next) {
398 next->previous = previous;
399 free_list_set_checksum(szone, next);
400 }
401 MSIZE_FLAGS_FOR_PTR(FOLLOWING_PTR(ptr, msize)) &= ~ PREV_FREE;
402 }
403
404 static boolean_t free_list_check(szone_t *szone, grain_t grain) {
405 unsigned count = 0;
406 free_list_t *ptr = szone->free_list[grain];
407 free_list_t *previous = NULL;
408 while (ptr) {
409 msize_t msize_and_free = MSIZE_FLAGS_FOR_PTR(ptr);
410 count++;
411 if (!(msize_and_free & THIS_FREE)) {
412 malloc_printf("*** malloc[%d]: In-use ptr in free list grain=%d count=%d ptr=%p\n", getpid(), grain, count, ptr);
413 return 0;
414 }
415 if (((unsigned)ptr) & (QUANTUM - 1)) {
416 malloc_printf("*** malloc[%d]: Unaligned ptr in free list grain=%d count=%d ptr=%p\n", getpid(), grain, count, ptr);
417 return 0;
418 }
419 if (!region_for_ptr_no_lock(szone, ptr)) {
420 malloc_printf("*** malloc[%d]: Ptr not in szone grain=%d count=%d ptr=%p\n", getpid(), grain, count, ptr);
421 return 0;
422 }
423 free_list_checksum(szone, ptr);
424 if (ptr->previous != previous) {
425 malloc_printf("*** malloc[%d]: Previous incorrectly set grain=%d count=%d ptr=%p\n", getpid(), grain, count, ptr);
426 return 0;
427 }
428 if ((grain != MAX_GRAIN-1) && (msize_and_free != (msize_for_grain(szone, grain) | THIS_FREE))) {
429 malloc_printf("*** malloc[%d]: Incorrect msize for grain=%d count=%d ptr=%p msize=%d\n", getpid(), grain, count, ptr, msize_and_free);
430 return 0;
431 }
432 previous = ptr;
433 ptr = ptr->next;
434 }
435 return 1;
436 }
437
438 /********************* SMALL BLOCKS MANAGEMENT ************************/
439
440 static INLINE region_t *region_for_ptr_no_lock(szone_t *szone, const void *ptr) {
441 region_t *first_region = szone->regions;
442 region_t *region = first_region + szone->last_region_hit;
443 region_t this = *region;
444 if ((unsigned)ptr - (unsigned)REGION_ADDRESS(this) < (unsigned)REGION_SIZE) {
445 return region;
446 } else {
447 // We iterate in reverse order becase last regions are more likely
448 region = first_region + szone->num_regions;
449 while (region != first_region) {
450 this = *(--region);
451 if ((unsigned)ptr - (unsigned)REGION_ADDRESS(this) < (unsigned)REGION_SIZE) {
452 szone->last_region_hit = region - first_region;
453 return region;
454 }
455 }
456 return NULL;
457 }
458 }
459
460 static INLINE void small_free_no_lock(szone_t *szone, region_t *region, void *ptr, msize_t msize_and_free) {
461 msize_t msize = msize_and_free & ~ PREV_FREE;
462 size_t original_size = msize << SHIFT_QUANTUM;
463 void *next_block = ((char *)ptr + original_size);
464 msize_t next_msize_and_free;
465 #if DEBUG_MALLOC
466 if (LOG(szone,ptr)) malloc_printf("In small_free_no_lock(), ptr=%p, msize=%d\n", ptr, msize);
467 if (msize < MIN_BLOCK) {
468 malloc_printf("In small_free_no_lock(), ptr=%p, msize=%d\n", ptr, msize);
469 szone_error(szone, "Trying to free small block that is too small", ptr);
470 }
471 #endif
472 if (((vm_address_t)next_block < REGION_END(*region)) && ((next_msize_and_free = MSIZE_FLAGS_FOR_PTR(next_block)) & THIS_FREE)) {
473 // If the next block is free, we coalesce
474 msize_t next_msize = next_msize_and_free & ~THIS_FREE;
475 if (LOG(szone,ptr)) malloc_printf("In small_free_no_lock(), for ptr=%p, msize=%d coalesced next block=%p next_msize=%d\n", ptr, msize, next_block, next_msize);
476 free_list_remove_ptr(szone, next_block, next_msize);
477 msize += next_msize;
478 }
479 // Let's try to coalesce backwards now
480 if (msize_and_free & PREV_FREE) {
481 msize_t previous_msize = PREVIOUS_MSIZE(ptr);
482 void *previous = ptr - (previous_msize << SHIFT_QUANTUM);
483 #if DEBUG_MALLOC
484 if (LOG(szone,previous)) malloc_printf("In small_free_no_lock(), coalesced backwards for %p previous=%p, msize=%d\n", ptr, previous, previous_msize);
485 if (!previous_msize || (previous_msize >= (((vm_address_t)ptr - REGION_ADDRESS(*region)) >> SHIFT_QUANTUM))) {
486 szone_error(szone, "Invariant 1 broken when coalescing backwards", ptr);
487 }
488 if (MSIZE_FLAGS_FOR_PTR(previous) != (previous_msize | THIS_FREE)) {
489 malloc_printf("previous=%p its_msize_and_free=0x%x previous_msize=%d\n", previous, MSIZE_FLAGS_FOR_PTR(previous), previous_msize);
490 szone_error(szone, "Invariant 3 broken when coalescing backwards", ptr);
491 }
492 #endif
493 free_list_remove_ptr(szone, previous, previous_msize);
494 ptr = previous;
495 msize += previous_msize;
496 #if DEBUG_MALLOC
497 if (msize & PREV_FREE) {
498 malloc_printf("In small_free_no_lock(), after coalescing with previous ptr=%p, msize=%d previous_msize=%d\n", ptr, msize, previous_msize);
499 szone_error(szone, "Incorrect coalescing", ptr);
500 }
501 #endif
502 }
503 if (szone->debug_flags & SCALABLE_MALLOC_DO_SCRIBBLE) {
504 if (!msize) {
505 szone_error(szone, "Incorrect size information - block header was damaged", ptr);
506 } else {
507 memset(ptr, 0x55, (msize << SHIFT_QUANTUM) - PTR_HEADER_SIZE);
508 }
509 }
510 free_list_add_ptr(szone, ptr, msize);
511 CHECK(szone, "small_free_no_lock: added to free list");
512 szone->num_small_objects--;
513 szone->num_bytes_in_small_objects -= original_size; // we use original_size and not msize to avoid double counting the coalesced blocks
514 }
515
516 static void *small_malloc_from_region_no_lock(szone_t *szone, msize_t msize) {
517 // Allocates from the last region or a freshly allocated region
518 region_t *last_region = szone->regions + szone->num_regions - 1;
519 vm_address_t new_address;
520 void *ptr;
521 msize_t msize_and_free;
522 unsigned region_capacity;
523 ptr = (void *)(REGION_END(*last_region) - szone->num_bytes_free_in_last_region + PTR_HEADER_SIZE);
524 #if DEBUG_MALLOC
525 if (((vm_address_t)ptr) & (QUANTUM - 1)) {
526 szone_error(szone, "Invariant broken while using end of region", ptr);
527 }
528 #endif
529 msize_and_free = MSIZE_FLAGS_FOR_PTR(ptr);
530 #if DEBUG_MALLOC
531 if (msize_and_free != PREV_FREE && msize_and_free != 0) {
532 malloc_printf("*** malloc[%d]: msize_and_free = %d\n", getpid(), msize_and_free);
533 szone_error(szone, "Invariant broken when allocating at end of zone", ptr);
534 }
535 #endif
536 // In order to make sure we don't have 2 free pointers following themselves, if the last item is a free item, we combine it and clear it
537 if (msize_and_free == PREV_FREE) {
538 msize_t previous_msize = PREVIOUS_MSIZE(ptr);
539 void *previous = ptr - (previous_msize << SHIFT_QUANTUM);
540 #if DEBUG_MALLOC
541 if (LOG(szone, ptr)) malloc_printf("Combining last with free space at %p\n", ptr);
542 if (!previous_msize || (previous_msize >= (((vm_address_t)ptr - REGION_ADDRESS(*last_region)) >> SHIFT_QUANTUM)) || (MSIZE_FLAGS_FOR_PTR(previous) != (previous_msize | THIS_FREE))) {
543 szone_error(szone, "Invariant broken when coalescing backwards at end of zone", ptr);
544 }
545 #endif
546 free_list_remove_ptr(szone, previous, previous_msize);
547 szone->num_bytes_free_in_last_region += previous_msize << SHIFT_QUANTUM;
548 memset(previous, 0, previous_msize << SHIFT_QUANTUM);
549 MSIZE_FLAGS_FOR_PTR(previous) = 0;
550 ptr = previous;
551 }
552 // first try at the end of the last region
553 CHECK(szone, __PRETTY_FUNCTION__);
554 if (szone->num_bytes_free_in_last_region >= (msize << SHIFT_QUANTUM)) {
555 szone->num_bytes_free_in_last_region -= (msize << SHIFT_QUANTUM);
556 szone->num_small_objects++;
557 szone->num_bytes_in_small_objects += msize << SHIFT_QUANTUM;
558 MSIZE_FLAGS_FOR_PTR(ptr) = msize;
559 return ptr;
560 }
561 // time to create a new region
562 new_address = allocate_pages(szone, REGION_SIZE, 0, VM_MAKE_TAG(VM_MEMORY_MALLOC_SMALL));
563 if (!new_address) {
564 // out of memory!
565 return NULL;
566 }
567 // let's prepare to free the remnants of last_region
568 if (szone->num_bytes_free_in_last_region >= QUANTUM) {
569 msize_t this_msize = szone->num_bytes_free_in_last_region >> SHIFT_QUANTUM;
570 // malloc_printf("Entering last block %p size=%d\n", ptr, this_msize << SHIFT_QUANTUM);
571 if (this_msize >= MIN_BLOCK) {
572 free_list_add_ptr(szone, ptr, this_msize);
573 } else {
574 // malloc_printf("Leaking last block at %p\n", ptr);
575 }
576 szone->num_bytes_free_in_last_region -= this_msize << SHIFT_QUANTUM; // to avoid coming back here
577 }
578 last_region[1] = new_address;
579 szone->num_regions++;
580 szone->num_bytes_free_in_last_region = REGION_SIZE - QUANTUM + PTR_HEADER_SIZE - (msize << SHIFT_QUANTUM);
581 ptr = (void *)(new_address + QUANTUM); // waste the first bytes
582 region_capacity = (MSIZE_FLAGS_FOR_PTR(szone->regions) * QUANTUM - PTR_HEADER_SIZE) / sizeof(region_t);
583 if (szone->num_regions >= region_capacity) {
584 unsigned new_capacity = region_capacity * 2 + 1;
585 msize_t new_msize = (new_capacity * sizeof(region_t) + PTR_HEADER_SIZE + QUANTUM - 1) / QUANTUM;
586 region_t *new_regions = ptr;
587 // malloc_printf("Now %d regions growing regions %p to %d\n", szone->num_regions, szone->regions, new_capacity);
588 MSIZE_FLAGS_FOR_PTR(new_regions) = new_msize;
589 szone->num_small_objects++;
590 szone->num_bytes_in_small_objects += new_msize << SHIFT_QUANTUM;
591 memcpy(new_regions, szone->regions, szone->num_regions * sizeof(region_t));
592 // We intentionally leak the previous regions pointer to avoid multi-threading crashes if another thread was reading it (unlocked) while we are changing it
593 // Given that in practise the number of regions is typically a handful, this should not be a big deal
594 szone->regions = new_regions;
595 ptr += (new_msize << SHIFT_QUANTUM);
596 szone->num_bytes_free_in_last_region -= (new_msize << SHIFT_QUANTUM);
597 // malloc_printf("Regions is now %p next ptr is %p\n", szone->regions, ptr);
598 }
599 szone->num_small_objects++;
600 szone->num_bytes_in_small_objects += msize << SHIFT_QUANTUM;
601 MSIZE_FLAGS_FOR_PTR(ptr) = msize;
602 return ptr;
603 }
604
605 static boolean_t szone_check_region(szone_t *szone, region_t *region) {
606 void *ptr = (void *)REGION_ADDRESS(*region) + QUANTUM;
607 vm_address_t region_end = REGION_END(*region);
608 int is_last_region = region == szone->regions + szone->num_regions - 1;
609 msize_t prev_free = 0;
610 while ((vm_address_t)ptr < region_end) {
611 msize_t msize_and_free = MSIZE_FLAGS_FOR_PTR(ptr);
612 if (!(msize_and_free & THIS_FREE)) {
613 msize_t msize = msize_and_free & ~PREV_FREE;
614 if ((msize_and_free & PREV_FREE) != prev_free) {
615 malloc_printf("*** malloc[%d]: invariant broken for %p (prev_free=%d) this msize=%d\n", getpid(), ptr, prev_free, msize_and_free);
616 return 0;
617 }
618 if (!msize) {
619 int extra = (is_last_region) ? szone->num_bytes_free_in_last_region : QUANTUM;
620 if (((unsigned)(ptr + extra)) < region_end) {
621 malloc_printf("*** malloc[%d]: invariant broken at region end: ptr=%p extra=%d index=%d num_regions=%d end=%p\n", getpid(), ptr, extra, region - szone->regions, szone->num_regions, (void *)region_end);
622 return 0;
623 }
624 break; // last encountered
625 }
626 if (msize > (LARGE_THRESHOLD / QUANTUM)) {
627 malloc_printf("*** malloc[%d]: invariant broken for %p this msize=%d - size is too large\n", getpid(), ptr, msize_and_free);
628 return 0;
629 }
630 if ((msize < MIN_BLOCK) && ((unsigned)ptr != region_end - QUANTUM)) {
631 malloc_printf("*** malloc[%d]: invariant broken for %p this msize=%d - size is too small\n", getpid(), ptr, msize_and_free);
632 return 0;
633 }
634 ptr += msize << SHIFT_QUANTUM;
635 prev_free = 0;
636 if (is_last_region && ((vm_address_t)ptr - PTR_HEADER_SIZE > region_end - szone->num_bytes_free_in_last_region)) {
637 malloc_printf("*** malloc[%d]: invariant broken for %p this msize=%d - block extends beyond allocated region\n", getpid(), ptr, msize_and_free);
638 }
639 } else {
640 // free pointer
641 msize_t msize = msize_and_free & ~THIS_FREE;
642 free_list_t *free_head = ptr;
643 msize_t *follower = (void *)FOLLOWING_PTR(ptr, msize);
644 if ((msize_and_free & PREV_FREE) && !prev_free) {
645 malloc_printf("*** malloc[%d]: invariant broken for free block %p this msize=%d: PREV_FREE set while previous block is in use\n", getpid(), ptr, msize);
646 return 0;
647 }
648 if (msize < MIN_BLOCK) {
649 malloc_printf("*** malloc[%d]: invariant broken for free block %p this msize=%d\n", getpid(), ptr, msize);
650 return 0;
651 }
652 if (prev_free) {
653 malloc_printf("*** malloc[%d]: invariant broken for %p (2 free in a row)\n", getpid(), ptr);
654 return 0;
655 }
656 free_list_checksum(szone, free_head);
657 if (free_head->previous && !(MSIZE_FLAGS_FOR_PTR(free_head->previous) & THIS_FREE)) {
658 malloc_printf("*** malloc[%d]: invariant broken for %p (previous %p is not a free pointer)\n", getpid(), ptr, free_head->previous);
659 return 0;
660 }
661 if (free_head->next && !(MSIZE_FLAGS_FOR_PTR(free_head->next) & THIS_FREE)) {
662 malloc_printf("*** malloc[%d]: invariant broken for %p (next is not a free pointer)\n", getpid(), ptr);
663 return 0;
664 }
665 if (PREVIOUS_MSIZE(follower) != msize) {
666 malloc_printf("*** malloc[%d]: invariant broken for free %p followed by %p in region [%x-%x] (end marker incorrect) should be %d; in fact %d\n", getpid(), ptr, follower, REGION_ADDRESS(*region), region_end, msize, PREVIOUS_MSIZE(follower));
667 return 0;
668 }
669 ptr = follower;
670 prev_free = PREV_FREE;
671 }
672 }
673 return 1;
674 }
675
676 static kern_return_t small_in_use_enumerator(task_t task, void *context, unsigned type_mask, vm_address_t region_address, unsigned num_regions, memory_reader_t reader, vm_range_recorder_t recorder) {
677 region_t *regions;
678 unsigned index = 0;
679 vm_range_t buffer[MAX_RECORDER_BUFFER];
680 unsigned count = 0;
681 kern_return_t err;
682 err = reader(task, region_address, sizeof(region_t) * num_regions, (void **)&regions);
683 if (err) return err;
684 while (index < num_regions) {
685 region_t region = regions[index++];
686 vm_range_t range = {REGION_ADDRESS(region), REGION_SIZE};
687 vm_address_t start = range.address + QUANTUM;
688 // malloc_printf("Enumerating small ptrs for Region starting at 0x%x\n", start);
689 if (type_mask & MALLOC_PTR_REGION_RANGE_TYPE) recorder(task, context, MALLOC_PTR_REGION_RANGE_TYPE, &range, 1);
690 if (type_mask & MALLOC_PTR_IN_USE_RANGE_TYPE) while (start < range.address + range.size) {
691 void *previous;
692 msize_t msize_and_free;
693 err = reader(task, start - PTR_HEADER_SIZE, QUANTUM, (void **)&previous);
694 if (err) return err;
695 previous += PTR_HEADER_SIZE;
696 msize_and_free = MSIZE_FLAGS_FOR_PTR(previous);
697 if (!(msize_and_free & THIS_FREE)) {
698 // Block in use
699 msize_t msize = msize_and_free & ~PREV_FREE;
700 if (!msize) break; // last encountered
701 buffer[count].address = start;
702 buffer[count].size = (msize << SHIFT_QUANTUM) - PTR_HEADER_SIZE;
703 count++;
704 if (count >= MAX_RECORDER_BUFFER) {
705 recorder(task, context, MALLOC_PTR_IN_USE_RANGE_TYPE, buffer, count);
706 count = 0;
707 }
708 start += msize << SHIFT_QUANTUM;
709 } else {
710 // free pointer
711 msize_t msize = msize_and_free & ~THIS_FREE;
712 start += msize << SHIFT_QUANTUM;
713 }
714 }
715 // malloc_printf("End region - count=%d\n", count);
716 }
717 if (count) recorder(task, context, MALLOC_PTR_IN_USE_RANGE_TYPE, buffer, count);
718 return 0;
719 }
720
721 static INLINE void *small_malloc_from_free_list(szone_t *szone, msize_t msize, boolean_t *locked) {
722 void *ptr;
723 msize_t this_msize;
724 free_list_t **free_list;
725 free_list_t **limit = szone->free_list + MAX_GRAIN - 1;
726 // first try the small grains
727 free_list = szone->free_list + grain_for_msize(szone, msize);
728 while (free_list < limit) {
729 // try bigger grains
730 ptr = *free_list;
731 if (ptr) {
732 if (!*locked) { *locked = 1; SZONE_LOCK(szone); CHECK(szone, __PRETTY_FUNCTION__); }
733 ptr = *free_list;
734 if (ptr) {
735 // optimistic test worked
736 free_list_t *next;
737 next = ((free_list_t *)ptr)->next;
738 if (next) {
739 next->previous = NULL;
740 free_list_set_checksum(szone, next);
741 }
742 *free_list = next;
743 this_msize = MSIZE_FLAGS_FOR_PTR(ptr) & ~THIS_FREE;
744 MSIZE_FLAGS_FOR_PTR(FOLLOWING_PTR(ptr, this_msize)) &= ~ PREV_FREE;
745 goto add_leftover_and_proceed;
746 }
747 }
748 free_list++;
749 }
750 // We now check the large grains for one that is big enough
751 if (!*locked) { *locked = 1; SZONE_LOCK(szone); CHECK(szone, __PRETTY_FUNCTION__); }
752 ptr = *free_list;
753 while (ptr) {
754 this_msize = MSIZE_FLAGS_FOR_PTR(ptr) & ~THIS_FREE;
755 if (this_msize >= msize) {
756 free_list_remove_ptr(szone, ptr, this_msize);
757 goto add_leftover_and_proceed;
758 }
759 ptr = ((free_list_t *)ptr)->next;
760 }
761 return NULL;
762 add_leftover_and_proceed:
763 if (this_msize >= msize + MIN_BLOCK) {
764 if (LOG(szone,ptr)) malloc_printf("In small_malloc_should_clear(), adding leftover ptr=%p, this_msize=%d\n", ptr, this_msize);
765 free_list_add_ptr(szone, ptr + (msize << SHIFT_QUANTUM), this_msize - msize);
766 this_msize = msize;
767 }
768 szone->num_small_objects++;
769 szone->num_bytes_in_small_objects += this_msize << SHIFT_QUANTUM;
770 #if DEBUG_MALLOC
771 if (LOG(szone,ptr)) malloc_printf("In small_malloc_should_clear(), ptr=%p, this_msize=%d, msize=%d\n", ptr, this_msize, msize);
772 #endif
773 MSIZE_FLAGS_FOR_PTR(ptr) = this_msize;
774 return ptr;
775 }
776
777 static INLINE void *small_malloc_should_clear(szone_t *szone, msize_t msize, boolean_t cleared_requested) {
778 boolean_t locked = 0;
779 void *ptr;
780 #if DEBUG_MALLOC
781 if (! (msize & 0xffff)) {
782 szone_error(szone, "Invariant broken (!msize) in allocation (region)", NULL);
783 }
784 if (msize < MIN_BLOCK) {
785 szone_error(szone, "Invariant broken (msize too small) in allocation (region)", NULL);
786 }
787 #endif
788 ptr = small_malloc_from_free_list(szone, msize, &locked);
789 if (ptr) {
790 CHECK(szone, __PRETTY_FUNCTION__);
791 SZONE_UNLOCK(szone);
792 if (cleared_requested) memset(ptr, 0, (msize << SHIFT_QUANTUM) - PTR_HEADER_SIZE);
793 return ptr;
794 } else {
795 if (!locked) SZONE_LOCK(szone);
796 CHECK(szone, __PRETTY_FUNCTION__);
797 ptr = small_malloc_from_region_no_lock(szone, msize);
798 // we don't clear because this freshly allocated space is pristine
799 CHECK(szone, __PRETTY_FUNCTION__);
800 SZONE_UNLOCK(szone);
801 }
802 return ptr;
803 }
804
805 static INLINE void *small_malloc_cleared_no_lock(szone_t *szone, msize_t msize) {
806 // tries to allocate a small, cleared block
807 boolean_t locked = 1;
808 void *ptr;
809 ptr = small_malloc_from_free_list(szone, msize, &locked);
810 if (ptr) {
811 memset(ptr, 0, (msize << SHIFT_QUANTUM) - PTR_HEADER_SIZE);
812 return ptr;
813 } else {
814 ptr = small_malloc_from_region_no_lock(szone, msize);
815 // we don't clear because this freshly allocated space is pristine
816 }
817 return ptr;
818 }
819
820 /********************* LARGE ENTRY UTILITIES ************************/
821
822 #if DEBUG_MALLOC
823
824 static void large_cache_debug_print(szone_t *szone) {
825 unsigned index = LARGE_CACHE_SIZE;
826 malloc_printf("Cache to be dealloced: ");
827 while (index--) {
828 vm_range_t range = szone->large_to_deallocate[index];
829 if (range.size) malloc_printf("0x%x(%dKB) ", range.address, range.size/1024);
830 }
831 malloc_printf("\n");
832 }
833
834 static void large_debug_print(szone_t *szone) {
835 unsigned num_large_entries = szone->num_large_entries;
836 unsigned index = num_large_entries;
837 while (index--) {
838 large_entry_t *range = szone->large_entries + index;
839 large_entry_t entry = *range;
840 if (!LARGE_ENTRY_IS_EMPTY(entry)) malloc_printf("%d: 0x%x(%dKB); ", index, LARGE_ENTRY_ADDRESS(entry), LARGE_ENTRY_SIZE(entry)/1024);
841 }
842 malloc_printf("\n");
843 }
844 #endif
845
846 static large_entry_t *large_entry_for_pointer_no_lock(szone_t *szone, const void *ptr) {
847 // result only valid during a lock
848 unsigned num_large_entries = szone->num_large_entries;
849 unsigned hash_index;
850 unsigned index;
851 if (!num_large_entries) return NULL;
852 hash_index = ((unsigned)ptr >> vm_page_shift) % num_large_entries;
853 index = hash_index;
854 do {
855 large_entry_t *range = szone->large_entries + index;
856 large_entry_t entry = *range;
857 if (LARGE_ENTRY_MATCHES(entry, ptr)) return range;
858 if (LARGE_ENTRY_IS_EMPTY(entry)) return NULL; // end of chain
859 index++; if (index == num_large_entries) index = 0;
860 } while (index != hash_index);
861 return NULL;
862 }
863
864 static void large_entry_insert_no_lock(szone_t *szone, large_entry_t range) {
865 unsigned num_large_entries = szone->num_large_entries;
866 unsigned hash_index = (range.address_and_num_pages >> vm_page_shift) % num_large_entries;
867 unsigned index = hash_index;
868 // malloc_printf("Before insertion of 0x%x\n", LARGE_ENTRY_ADDRESS(range));
869 do {
870 large_entry_t *entry = szone->large_entries + index;
871 if (LARGE_ENTRY_IS_EMPTY(*entry)) {
872 *entry = range;
873 return; // end of chain
874 }
875 index++; if (index == num_large_entries) index = 0;
876 } while (index != hash_index);
877 }
878
879 static INLINE void large_entries_rehash_after_entry_no_lock(szone_t *szone, large_entry_t *entry) {
880 unsigned num_large_entries = szone->num_large_entries;
881 unsigned hash_index = entry - szone->large_entries;
882 unsigned index = hash_index;
883 do {
884 large_entry_t range;
885 index++; if (index == num_large_entries) index = 0;
886 range = szone->large_entries[index];
887 if (LARGE_ENTRY_IS_EMPTY(range)) return;
888 szone->large_entries[index].address_and_num_pages = 0;
889 large_entry_insert_no_lock(szone, range); // this will reinsert in the proper place
890 } while (index != hash_index);
891 }
892
893 static INLINE large_entry_t *large_entries_alloc_no_lock(szone_t *szone, unsigned num) {
894 size_t size = num * sizeof(large_entry_t);
895 boolean_t is_vm_allocation = size >= LARGE_THRESHOLD;
896 if (is_vm_allocation) {
897 return (void *)allocate_pages(szone, round_page(size), 0, VM_MAKE_TAG(VM_MEMORY_MALLOC_LARGE));
898 } else {
899 return small_malloc_cleared_no_lock(szone, (size + PTR_HEADER_SIZE + QUANTUM - 1) >> SHIFT_QUANTUM);
900 }
901 }
902
903 static void large_entries_free_no_lock(szone_t *szone, large_entry_t *entries, unsigned num) {
904 size_t size = num * sizeof(large_entry_t);
905 boolean_t is_vm_allocation = size >= LARGE_THRESHOLD;
906 if (is_vm_allocation) {
907 deallocate_pages(szone, (vm_address_t)entries, round_page(size), 0);
908 } else {
909 region_t *region = region_for_ptr_no_lock(szone, entries);
910 msize_t msize_and_free = MSIZE_FLAGS_FOR_PTR(entries);
911 if (msize_and_free & THIS_FREE) {
912 szone_error(szone, "Object already freed being freed", entries);
913 return;
914 }
915 small_free_no_lock(szone, region, entries, msize_and_free);
916 }
917 }
918
919 static void large_entries_grow_no_lock(szone_t *szone) {
920 unsigned old_num_entries = szone->num_large_entries;
921 large_entry_t *old_entries = szone->large_entries;
922 unsigned new_num_entries = (old_num_entries) ? old_num_entries * 2 + 1 : 15; // always an odd number for good hashing
923 large_entry_t *new_entries = large_entries_alloc_no_lock(szone, new_num_entries);
924 unsigned index = old_num_entries;
925 szone->num_large_entries = new_num_entries;
926 szone->large_entries = new_entries;
927 // malloc_printf("_grow_large_entries old_num_entries=%d new_num_entries=%d\n", old_num_entries, new_num_entries);
928 while (index--) {
929 large_entry_t oldRange = old_entries[index];
930 if (!LARGE_ENTRY_IS_EMPTY(oldRange)) large_entry_insert_no_lock(szone, oldRange);
931 }
932 if (old_entries) large_entries_free_no_lock(szone, old_entries, old_num_entries);
933 }
934
935 static vm_range_t large_free_no_lock(szone_t *szone, large_entry_t *entry) {
936 // frees the specific entry in the size table
937 // returns a range to truly deallocate, taking into account
938 vm_range_t range;
939 range.address = LARGE_ENTRY_ADDRESS(*entry);
940 range.size = LARGE_ENTRY_SIZE(*entry);
941 szone->num_large_objects_in_use --;
942 szone->num_bytes_in_large_objects -= range.size;
943 if (szone->debug_flags & SCALABLE_MALLOC_ADD_GUARD_PAGES) {
944 protect(szone, range.address, range.size, VM_PROT_READ | VM_PROT_WRITE, szone->debug_flags);
945 range.address -= vm_page_size;
946 range.size += 2 * vm_page_size;
947 }
948 // printf("Entry is 0x%x=%d; cache is 0x%x ; found=0x%x\n", entry, entry-szone->large_entries, szone->large_entries, large_entry_for_pointer_no_lock(szone, (void *)range.address));
949 entry->address_and_num_pages = 0;
950 large_entries_rehash_after_entry_no_lock(szone, entry);
951 #if DEBUG_MALLOC
952 if (large_entry_for_pointer_no_lock(szone, (void *)range.address)) {
953 malloc_printf("*** malloc[%d]: Freed entry 0x%x still in use; num_large_entries=%d\n", getpid(), range.address, szone->num_large_entries);
954 large_cache_debug_print(szone);
955 large_debug_print(szone);
956 sleep(3600);
957 }
958 #endif
959 return range;
960 }
961
962 static vm_range_t large_find_better_range_to_deallocate(szone_t *szone, vm_range_t range) {
963 // enters the specified large entry into the cache of freed entries
964 // returns a range to truly deallocate
965 vm_range_t *range_to_use;
966 vm_range_t vm_range_to_deallocate;
967
968 // if the specified range in larger than MAX_LARGE_SIZE_TO_CACHE the range is not cached
969 if (range.size > MAX_LARGE_SIZE_TO_CACHE) return range;
970
971 range = coalesce_range(szone->large_to_deallocate, LARGE_CACHE_SIZE, range);
972 range_to_use = first_zero_range(szone->large_to_deallocate, LARGE_CACHE_SIZE);
973 if (range_to_use) {
974 // we fill an empty slot
975 *range_to_use = range;
976 return zero_range;
977 }
978 // we always try to deallocate the largest chunk
979 range_to_use = largest_range(szone->large_to_deallocate, LARGE_CACHE_SIZE);
980 if (!range_to_use) return range;
981 vm_range_to_deallocate = *range_to_use;
982 *range_to_use = range;
983 return vm_range_to_deallocate;
984 }
985
986 static kern_return_t large_in_use_enumerator(task_t task, void *context, unsigned type_mask, vm_address_t large_entries_address, unsigned num_entries, memory_reader_t reader, vm_range_recorder_t recorder) {
987 unsigned index = 0;
988 vm_range_t buffer[MAX_RECORDER_BUFFER];
989 unsigned count = 0;
990 large_entry_t *entries;
991 kern_return_t err;
992 err = reader(task, large_entries_address, sizeof(large_entry_t) * num_entries, (void **)&entries);
993 if (err) return err;
994 index = num_entries;
995 if ((type_mask & MALLOC_ADMIN_REGION_RANGE_TYPE) && (num_entries * sizeof(large_entry_t) >= LARGE_THRESHOLD)) {
996 vm_range_t range;
997 range.address = large_entries_address;
998 range.size = round_page(num_entries * sizeof(large_entry_t));
999 recorder(task, context, MALLOC_ADMIN_REGION_RANGE_TYPE, &range, 1);
1000 }
1001 if (type_mask & (MALLOC_PTR_IN_USE_RANGE_TYPE | MALLOC_PTR_REGION_RANGE_TYPE)) while (index--) {
1002 large_entry_t entry = entries[index];
1003 if (!LARGE_ENTRY_IS_EMPTY(entry)) {
1004 vm_range_t range;
1005 range.address = LARGE_ENTRY_ADDRESS(entry);
1006 range.size = LARGE_ENTRY_SIZE(entry);
1007 buffer[count++] = range;
1008 if (count >= MAX_RECORDER_BUFFER) {
1009 recorder(task, context, MALLOC_PTR_IN_USE_RANGE_TYPE | MALLOC_PTR_REGION_RANGE_TYPE, buffer, count);
1010 count = 0;
1011 }
1012 }
1013 }
1014 if (count) recorder(task, context, MALLOC_PTR_IN_USE_RANGE_TYPE | MALLOC_PTR_REGION_RANGE_TYPE, buffer, count);
1015 return 0;
1016 }
1017
1018 /********************* HUGE ENTRY UTILITIES ************************/
1019
1020 static huge_entry_t *huge_entry_for_pointer_no_lock(szone_t *szone, const void *ptr) {
1021 unsigned index = szone->num_huge_entries;
1022 while (index--) {
1023 huge_entry_t *huge = szone->huge_entries + index;
1024 if (huge->address == (vm_address_t)ptr) return huge;
1025 }
1026 return NULL;
1027 }
1028
1029 static void huge_entry_append(szone_t *szone, huge_entry_t huge) {
1030 // We do a little dance with locking because doing allocation (even in the default szone) may cause something to get freed in this szone, with a deadlock
1031 huge_entry_t *new_huge_entries = NULL;
1032 SZONE_LOCK(szone);
1033 while (1) {
1034 unsigned num_huge_entries;
1035 num_huge_entries = szone->num_huge_entries;
1036 SZONE_UNLOCK(szone);
1037 // malloc_printf("In huge_entry_append currentEntries=%d\n", num_huge_entries);
1038 if (new_huge_entries) szone_free(szone, new_huge_entries);
1039 new_huge_entries = szone_malloc(szone, (num_huge_entries + 1) * sizeof(huge_entry_t));
1040 SZONE_LOCK(szone);
1041 if (num_huge_entries == szone->num_huge_entries) {
1042 // No change - our malloc still applies
1043 huge_entry_t *old_huge_entries = szone->huge_entries;
1044 if (num_huge_entries) memcpy(new_huge_entries, old_huge_entries, num_huge_entries * sizeof(huge_entry_t));
1045 new_huge_entries[szone->num_huge_entries++] = huge;
1046 szone->huge_entries = new_huge_entries;
1047 SZONE_UNLOCK(szone);
1048 szone_free(szone, old_huge_entries);
1049 // malloc_printf("Done huge_entry_append now=%d\n", szone->num_huge_entries);
1050 return;
1051 }
1052 // try again!
1053 }
1054 }
1055
1056 static kern_return_t huge_in_use_enumerator(task_t task, void *context, unsigned type_mask, vm_address_t huge_entries_address, unsigned num_entries, memory_reader_t reader, vm_range_recorder_t recorder) {
1057 huge_entry_t *entries;
1058 kern_return_t err;
1059 err = reader(task, huge_entries_address, sizeof(huge_entry_t) * num_entries, (void **)&entries);
1060 if (err) return err;
1061 if (num_entries) recorder(task, context, MALLOC_PTR_IN_USE_RANGE_TYPE | MALLOC_PTR_REGION_RANGE_TYPE, entries, num_entries);
1062 return 0;
1063 }
1064
1065 static void *large_and_huge_malloc(szone_t *szone, unsigned num_pages, boolean_t cleared_requested) {
1066 vm_address_t addr = 0;
1067 boolean_t cleared_needed = 0; // by default blocks will be freshly allocated and therefore no need to clean them
1068 if (!num_pages) num_pages = 1; // minimal allocation size for this szone
1069 // malloc_printf("In large_and_huge_malloc for %dKB\n", num_pages * vm_page_size / 1024);
1070 if (num_pages >= (1 << vm_page_shift)) {
1071 huge_entry_t huge;
1072 huge.size = num_pages << vm_page_shift;
1073 addr = allocate_pages(szone, huge.size, szone->debug_flags, VM_MAKE_TAG(VM_MEMORY_MALLOC_HUGE));
1074 if (!addr) return NULL;
1075 huge.address = addr;
1076 huge_entry_append(szone, huge);
1077 SZONE_LOCK(szone);
1078 szone->num_bytes_in_huge_objects += huge.size;
1079 } else {
1080 vm_size_t size = num_pages << vm_page_shift;
1081 large_entry_t entry;
1082 boolean_t add_guard_pages = szone->debug_flags & SCALABLE_MALLOC_ADD_GUARD_PAGES;
1083 vm_size_t guard_pages = (add_guard_pages) ? 2 * vm_page_size : 0;
1084 vm_range_t *range_to_use;
1085 cleared_needed = cleared_requested; // in the "large" case set by default
1086 SZONE_LOCK(szone);
1087 // First check in the list large_to_deallocate if we can reuse
1088 // malloc_printf("In szone_malloc checking recently deallocated\n");
1089 range_to_use = first_range_greater_or_equal(szone->large_to_deallocate, LARGE_CACHE_SIZE, size + guard_pages);
1090 if (range_to_use) {
1091 // that one will do!
1092 addr = range_to_use->address + ((add_guard_pages) ? vm_page_size : 0);
1093 if (add_guard_pages) protect(szone, addr, size, 0, szone->debug_flags);
1094 // malloc_printf("In szone_malloc found recently deallocated at 0x%x for %d pages\n", addr, num_pages);
1095 if (range_to_use->size == size + guard_pages) {
1096 *range_to_use = zero_range;
1097 } else {
1098 range_to_use->address += size + guard_pages;
1099 range_to_use->size -= size + guard_pages;
1100 }
1101 #if DEBUG_MALLOC
1102 if (large_entry_for_pointer_no_lock(szone, (void *)addr)) {
1103 malloc_printf("Entry about to be reused already in use: 0x%x\n", addr);
1104 large_debug_print(szone);
1105 sleep(3600);
1106 }
1107 #endif
1108 }
1109 if (!addr) {
1110 // we need to really allocate_pages a new region
1111 SZONE_UNLOCK(szone);
1112 addr = allocate_pages(szone, size, szone->debug_flags, VM_MAKE_TAG(VM_MEMORY_MALLOC_LARGE));
1113 cleared_needed = 0; // since we allocated the pages, no need to clean them
1114 if (LOG(szone, addr)) malloc_printf("In szone_malloc true large allocation at %p for %dKB\n", (void *)addr, size / 1024);
1115 SZONE_LOCK(szone);
1116 if (!addr) return NULL;
1117 #if DEBUG_MALLOC
1118 if (large_entry_for_pointer_no_lock(szone, (void *)addr)) {
1119 malloc_printf("Freshly allocated is already in use: 0x%x\n", addr);
1120 large_debug_print(szone);
1121 sleep(3600);
1122 }
1123 #endif
1124 }
1125 if ((szone->num_large_objects_in_use + 1) * 4 > szone->num_large_entries) {
1126 // density of hash table too high; grow table
1127 // we do that under lock to avoid a race
1128 // malloc_printf("In szone_malloc growing hash table current=%d\n", szone->num_large_entries);
1129 large_entries_grow_no_lock(szone);
1130 }
1131 // malloc_printf("Inserting large entry (0x%x, %dKB)\n", addr, num_pages * vm_page_size / 1024);
1132 entry.address_and_num_pages = addr | num_pages;
1133 #if DEBUG_MALLOC
1134 if (large_entry_for_pointer_no_lock(szone, (void *)addr)) {
1135 malloc_printf("Entry about to be added already in use: 0x%x\n", addr);
1136 large_debug_print(szone);
1137 sleep(3600);
1138 }
1139 #endif
1140 large_entry_insert_no_lock(szone, entry);
1141 #if DEBUG_MALLOC
1142 if (!large_entry_for_pointer_no_lock(szone, (void *)addr)) {
1143 malloc_printf("Can't find entry just added\n");
1144 large_debug_print(szone);
1145 sleep(3600);
1146 }
1147 #endif
1148 // malloc_printf("Inserted large entry (0x%x, %d pages)\n", addr, num_pages);
1149 szone->num_large_objects_in_use ++;
1150 szone->num_bytes_in_large_objects += size;
1151 }
1152 SZONE_UNLOCK(szone);
1153 if (cleared_needed) memset((void *)addr, 0, num_pages << vm_page_shift);
1154 return (void *)addr;
1155 }
1156
1157 /********************* Zone call backs ************************/
1158
1159 static void szone_free(szone_t *szone, void *ptr) {
1160 region_t *region;
1161 large_entry_t *entry;
1162 vm_range_t vm_range_to_deallocate;
1163 huge_entry_t *huge;
1164 if (LOG(szone, ptr)) malloc_printf("In szone_free with %p\n", ptr);
1165 if (!ptr) return;
1166 if ((vm_address_t)ptr & (QUANTUM - 1)) {
1167 szone_error(szone, "Non-aligned pointer being freed", ptr);
1168 return;
1169 }
1170 // try a small pointer
1171 region = region_for_ptr_no_lock(szone, ptr);
1172 if (region) {
1173 // this is indeed a valid pointer
1174 msize_t msize_and_free;
1175 SZONE_LOCK(szone);
1176 msize_and_free = MSIZE_FLAGS_FOR_PTR(ptr);
1177 if (msize_and_free & THIS_FREE) {
1178 szone_error(szone, "Object already freed being freed", ptr);
1179 return;
1180 }
1181 CHECK(szone, __PRETTY_FUNCTION__);
1182 small_free_no_lock(szone, region, ptr, msize_and_free);
1183 CHECK(szone, __PRETTY_FUNCTION__);
1184 SZONE_UNLOCK(szone);
1185 return;
1186 }
1187 if (((unsigned)ptr) & (vm_page_size - 1)) {
1188 szone_error(szone, "Non-page-aligned, non-allocated pointer being freed", ptr);
1189 return;
1190 }
1191 SZONE_LOCK(szone);
1192 entry = large_entry_for_pointer_no_lock(szone, ptr);
1193 if (entry) {
1194 // malloc_printf("Ready for deallocation [0x%x-%dKB]\n", LARGE_ENTRY_ADDRESS(*entry), LARGE_ENTRY_SIZE(*entry)/1024);
1195 if (KILL_THRESHOLD && (LARGE_ENTRY_SIZE(*entry) > KILL_THRESHOLD)) {
1196 // We indicate to the VM system that these pages contain garbage and therefore don't need to be swapped out
1197 vm_msync(mach_task_self(), LARGE_ENTRY_ADDRESS(*entry), LARGE_ENTRY_SIZE(*entry), VM_SYNC_KILLPAGES);
1198 }
1199 vm_range_to_deallocate = large_free_no_lock(szone, entry);
1200 vm_range_to_deallocate = large_find_better_range_to_deallocate(szone, vm_range_to_deallocate);
1201 #if DEBUG_MALLOC
1202 if (large_entry_for_pointer_no_lock(szone, ptr)) {
1203 malloc_printf("*** malloc[%d]: Just after freeing 0x%x still in use num_large_entries=%d\n", getpid(), ptr, szone->num_large_entries);
1204 large_cache_debug_print(szone);
1205 large_debug_print(szone);
1206 sleep(3600);
1207 }
1208 #endif
1209 } else if ((huge = huge_entry_for_pointer_no_lock(szone, ptr))) {
1210 vm_range_to_deallocate = *huge;
1211 *huge = szone->huge_entries[--szone->num_huge_entries]; // last entry fills that spot
1212 szone->num_bytes_in_huge_objects -= vm_range_to_deallocate.size;
1213 } else {
1214 #if DEBUG_MALLOC
1215 large_debug_print(szone);
1216 #endif
1217 szone_error(szone, "Pointer being freed was not allocated", ptr);
1218 return;
1219 }
1220 CHECK(szone, __PRETTY_FUNCTION__);
1221 SZONE_UNLOCK(szone); // we release the lock asap
1222 // we deallocate_pages, including guard pages
1223 if (vm_range_to_deallocate.address) {
1224 // malloc_printf("About to deallocate 0x%x size %dKB\n", vm_range_to_deallocate.address, vm_range_to_deallocate.size / 1024);
1225 #if DEBUG_MALLOC
1226 if (large_entry_for_pointer_no_lock(szone, (void *)vm_range_to_deallocate.address)) {
1227 malloc_printf("*** malloc[%d]: Invariant broken: 0x%x still in use num_large_entries=%d\n", getpid(), vm_range_to_deallocate.address, szone->num_large_entries);
1228 large_cache_debug_print(szone);
1229 large_debug_print(szone);
1230 sleep(3600);
1231 }
1232 #endif
1233 deallocate_pages(szone, vm_range_to_deallocate.address, vm_range_to_deallocate.size, 0);
1234 }
1235 }
1236
1237 static INLINE void *szone_malloc_should_clear(szone_t *szone, size_t size, boolean_t cleared_requested) {
1238 void *ptr;
1239 if (!((szone->debug_flags & SCALABLE_MALLOC_ADD_GUARD_PAGES) && PROTECT_SMALL) && (size < LARGE_THRESHOLD)) {
1240 // think small
1241 size_t msize = (size + PTR_HEADER_SIZE + QUANTUM - 1) >> SHIFT_QUANTUM;
1242 if (msize < MIN_BLOCK) msize = MIN_BLOCK;
1243 ptr = small_malloc_should_clear(szone, msize, cleared_requested);
1244 #if DEBUG_MALLOC
1245 if ((MSIZE_FLAGS_FOR_PTR(ptr) & ~ PREV_FREE) < msize) {
1246 malloc_printf("ptr=%p this=%d msize=%d\n", ptr, MSIZE_FLAGS_FOR_PTR(ptr), (int)msize);
1247 szone_error(szone, "Pointer allocated has improper size (1)", ptr);
1248 return NULL;
1249 }
1250 if ((MSIZE_FLAGS_FOR_PTR(ptr) & ~ PREV_FREE) < MIN_BLOCK) {
1251 malloc_printf("ptr=%p this=%d msize=%d\n", ptr, MSIZE_FLAGS_FOR_PTR(ptr), (int)msize);
1252 szone_error(szone, "Pointer allocated has improper size (2)", ptr);
1253 return NULL;
1254 }
1255 #endif
1256 } else {
1257 unsigned num_pages;
1258 num_pages = round_page(size) >> vm_page_shift;
1259 ptr = large_and_huge_malloc(szone, num_pages, cleared_requested);
1260 }
1261 if (LOG(szone, ptr)) malloc_printf("szone_malloc returned %p\n", ptr);
1262 return ptr;
1263 }
1264
1265 static void *szone_malloc(szone_t *szone, size_t size) {
1266 return szone_malloc_should_clear(szone, size, 0);
1267 }
1268
1269 static void *szone_calloc(szone_t *szone, size_t num_items, size_t size) {
1270 return szone_malloc_should_clear(szone, num_items * size, 1);
1271 }
1272
1273 static void *szone_valloc(szone_t *szone, size_t size) {
1274 void *ptr;
1275 unsigned num_pages;
1276 num_pages = round_page(size) >> vm_page_shift;
1277 ptr = large_and_huge_malloc(szone, num_pages, 1);
1278 if (LOG(szone, ptr)) malloc_printf("szone_valloc returned %p\n", ptr);
1279 return ptr;
1280 }
1281
1282 static size_t szone_size(szone_t *szone, const void *ptr) {
1283 size_t size = 0;
1284 region_t *region;
1285 large_entry_t *entry;
1286 huge_entry_t *huge;
1287 if (!ptr) return 0;
1288 if (LOG(szone, ptr)) malloc_printf("In szone_size for %p (szone=%p)\n", ptr, szone);
1289 if ((vm_address_t)ptr & (QUANTUM - 1)) return 0;
1290 if ((((unsigned)ptr) & (vm_page_size - 1)) && (MSIZE_FLAGS_FOR_PTR(ptr) & THIS_FREE)) {
1291 // not page aligned, but definitely not in use
1292 return 0;
1293 }
1294 // Try a small pointer
1295 region = region_for_ptr_no_lock(szone, ptr);
1296 // malloc_printf("FOUND REGION %p\n", region);
1297 if (region) {
1298 // this is indeed a valid pointer
1299 msize_t msize_and_free = MSIZE_FLAGS_FOR_PTR(ptr);
1300 return (msize_and_free & THIS_FREE) ? 0 : ((msize_and_free & ~PREV_FREE) << SHIFT_QUANTUM) - PTR_HEADER_SIZE;
1301 }
1302 if (((unsigned)ptr) & (vm_page_size - 1)) {
1303 return 0;
1304 }
1305 SZONE_LOCK(szone);
1306 entry = large_entry_for_pointer_no_lock(szone, ptr);
1307 if (entry) {
1308 size = LARGE_ENTRY_SIZE(*entry);
1309 } else if ((huge = huge_entry_for_pointer_no_lock(szone, ptr))) {
1310 size = huge->size;
1311 }
1312 SZONE_UNLOCK(szone);
1313 // malloc_printf("szone_size for large/huge %p returned %d\n", ptr, (unsigned)size);
1314 if (LOG(szone, ptr)) malloc_printf("szone_size for %p returned %d\n", ptr, (unsigned)size);
1315 return size;
1316 }
1317
1318 static INLINE int szone_try_realloc_in_place(szone_t *szone, void *ptr, size_t old_size, size_t new_size) {
1319 // returns 1 on success
1320 void *next_block = (char *)ptr + old_size + PTR_HEADER_SIZE;
1321 msize_t next_msize_and_free;
1322 msize_t next_msize;
1323 region_t region;
1324 msize_t coalesced_msize;
1325 msize_t leftover_msize;
1326 msize_t new_msize_and_free;
1327 void *following_ptr;
1328 SZONE_LOCK(szone);
1329 region = szone->regions[szone->num_regions - 1];
1330 if (((vm_address_t)ptr >= region) && ((vm_address_t)ptr < region + REGION_SIZE) && ((vm_address_t)next_block == REGION_END(region) - szone->num_bytes_free_in_last_region + PTR_HEADER_SIZE)) {
1331 // This could be optimized but it is so rare it's not worth it
1332 SZONE_UNLOCK(szone);
1333 return 0;
1334 }
1335 // If the next block is free, we coalesce
1336 next_msize_and_free = MSIZE_FLAGS_FOR_PTR(next_block);
1337 #if DEBUG_MALLOC
1338 if ((vm_address_t)next_block & (QUANTUM - 1)) {
1339 szone_error(szone, "Internal invariant broken in realloc(next_block)", next_block);
1340 }
1341 if (next_msize_and_free & PREV_FREE) {
1342 malloc_printf("szone_try_realloc_in_place: 0x%x=PREV_FREE|%d\n", next_msize_and_free, next_msize_and_free & ~PREV_FREE);
1343 SZONE_UNLOCK(szone);
1344 return 0;
1345 }
1346 #endif
1347 next_msize = next_msize_and_free & ~THIS_FREE;
1348 if (!(next_msize_and_free & THIS_FREE) || !next_msize || (old_size + (next_msize << SHIFT_QUANTUM) < new_size)) {
1349 SZONE_UNLOCK(szone);
1350 return 0;
1351 }
1352 coalesced_msize = (new_size - old_size + QUANTUM - 1) >> SHIFT_QUANTUM;
1353 leftover_msize = next_msize - coalesced_msize;
1354 new_msize_and_free = MSIZE_FLAGS_FOR_PTR(ptr);
1355 // malloc_printf("Realloc in place for %p; current msize=%d next_msize=%d wanted=%d\n", ptr, MSIZE_FLAGS_FOR_PTR(ptr), next_msize, new_size);
1356 free_list_remove_ptr(szone, next_block, next_msize);
1357 if ((leftover_msize < MIN_BLOCK) || (leftover_msize < coalesced_msize / 4)) {
1358 // don't bother splitting it off
1359 // malloc_printf("No leftover ");
1360 coalesced_msize = next_msize;
1361 leftover_msize = 0;
1362 } else {
1363 void *leftover = next_block + (coalesced_msize << SHIFT_QUANTUM);
1364 // malloc_printf("Leftover ");
1365 free_list_add_ptr(szone, leftover, leftover_msize);
1366 }
1367 new_msize_and_free += coalesced_msize;
1368 MSIZE_FLAGS_FOR_PTR(ptr) = new_msize_and_free;
1369 following_ptr = FOLLOWING_PTR(ptr, new_msize_and_free & ~PREV_FREE);
1370 MSIZE_FLAGS_FOR_PTR(following_ptr) &= ~ PREV_FREE;
1371 #if DEBUG_MALLOC
1372 {
1373 msize_t ms = MSIZE_FLAGS_FOR_PTR(following_ptr);
1374 msize_t pms = PREVIOUS_MSIZE(FOLLOWING_PTR(following_ptr, ms & ~THIS_FREE));
1375 malloc_printf("Following ptr of coalesced (%p) has msize_and_free=0x%x=%s%d end_of_block_marker=%d\n", following_ptr, ms, (ms & THIS_FREE) ? "THIS_FREE|" : "", ms & ~THIS_FREE, pms);
1376 }
1377 if (LOG(szone,ptr)) malloc_printf("In szone_realloc(), ptr=%p, msize=%d\n", ptr, MSIZE_FLAGS_FOR_PTR(ptr));
1378 #endif
1379 CHECK(szone, __PRETTY_FUNCTION__);
1380 szone->num_bytes_in_small_objects += coalesced_msize << SHIFT_QUANTUM;
1381 SZONE_UNLOCK(szone);
1382 // malloc_printf("Extended ptr %p for realloc old=%d desired=%d new=%d leftover=%d\n", ptr, (unsigned)old_size, (unsigned)new_size, (unsigned)szone_size(szone, ptr), leftover_msize << SHIFT_QUANTUM);
1383 return 1;
1384 }
1385
1386 static void *szone_realloc(szone_t *szone, void *ptr, size_t new_size) {
1387 size_t old_size = 0;
1388 void *newPtr;
1389 if (LOG(szone, ptr)) malloc_printf("In szone_realloc for %p, %d\n", ptr, (unsigned)new_size);
1390 if (!ptr) return szone_malloc(szone, new_size);
1391 old_size = szone_size(szone, ptr);
1392 if (!old_size) {
1393 szone_error(szone, "Pointer being reallocated was not allocated", ptr);
1394 return NULL;
1395 }
1396 if (old_size >= new_size) return ptr;
1397 if (!((szone->debug_flags & SCALABLE_MALLOC_ADD_GUARD_PAGES) && PROTECT_SMALL) && (new_size < LARGE_THRESHOLD)) {
1398 // We now try to realloc in place
1399 if (szone_try_realloc_in_place(szone, ptr, old_size, new_size)) return ptr;
1400 }
1401 newPtr = szone_malloc(szone, new_size);
1402 if ((old_size > VM_COPY_THRESHOLD) && (old_size < (1 << (vm_page_shift + vm_page_shift)))) {
1403 // we know it's a large block, and not a huge block
1404 kern_return_t err = 0;
1405 err = vm_copy(mach_task_self(), (vm_address_t)ptr, old_size, (vm_address_t)newPtr);
1406 if (err) {
1407 szone_error(szone, "Can't vm_copy region", ptr);
1408 } else {
1409 large_entry_t *entry;
1410 vm_range_t range;
1411 SZONE_LOCK(szone);
1412 entry = large_entry_for_pointer_no_lock(szone, ptr);
1413 if (!entry) {
1414 szone_error(szone, "Can't find entry for large copied block", ptr);
1415 }
1416 range = large_free_no_lock(szone, entry);
1417 SZONE_UNLOCK(szone); // we release the lock asap
1418 // we truly deallocate_pages, including guard pages
1419 deallocate_pages(szone, range.address, range.size, 0);
1420 if (LOG(szone, ptr)) malloc_printf("szone_realloc returned %p for %d\n", newPtr, (unsigned)new_size);
1421 return newPtr;
1422 }
1423 } else {
1424 memcpy(newPtr, ptr, old_size);
1425 }
1426 szone_free(szone, ptr);
1427 if (LOG(szone, ptr)) malloc_printf("szone_realloc returned %p for %d\n", newPtr, (unsigned)new_size);
1428 return newPtr;
1429 }
1430
1431 static void szone_destroy(szone_t *szone) {
1432 unsigned index;
1433 index = szone->num_large_entries;
1434 while (index--) {
1435 large_entry_t *entry = szone->large_entries + index;
1436 if (!LARGE_ENTRY_IS_EMPTY(*entry)) {
1437 large_entry_t range;
1438 range = *entry;
1439 // we deallocate_pages, including guard pages
1440 deallocate_pages(szone, LARGE_ENTRY_ADDRESS(range), LARGE_ENTRY_SIZE(range), szone->debug_flags);
1441 }
1442 }
1443 if (szone->num_large_entries * sizeof(large_entry_t) >= LARGE_THRESHOLD) large_entries_free_no_lock(szone, szone->large_entries, szone->num_large_entries); // we do not free in the small chunk case
1444 index = LARGE_CACHE_SIZE;
1445 while (index--) {
1446 vm_range_t range = szone->large_to_deallocate[index];
1447 if (range.size) deallocate_pages(szone, range.address, range.size, 0);
1448 }
1449 index = szone->num_huge_entries;
1450 while (index--) {
1451 huge_entry_t *huge = szone->huge_entries + index;
1452 deallocate_pages(szone, huge->address, huge->size, szone->debug_flags);
1453 }
1454 // and now we free regions, with regions[0] as the last one (the final harakiri)
1455 index = szone->num_regions;
1456 while (index--) { // we skip the first region, that is the zone itself
1457 region_t region = szone->regions[index];
1458 deallocate_pages(szone, REGION_ADDRESS(region), REGION_SIZE, 0);
1459 }
1460 }
1461
1462 static size_t szone_good_size(szone_t *szone, size_t size) {
1463 if (!((szone->debug_flags & SCALABLE_MALLOC_ADD_GUARD_PAGES) && PROTECT_SMALL) && (size < LARGE_THRESHOLD)) {
1464 // think small
1465 msize_t msize = (size + PTR_HEADER_SIZE + QUANTUM - 1) >> SHIFT_QUANTUM;
1466 if (msize < MIN_BLOCK) msize = MIN_BLOCK;
1467 return (msize << SHIFT_QUANTUM) - PTR_HEADER_SIZE;
1468 } else {
1469 unsigned num_pages;
1470 num_pages = round_page(size) >> vm_page_shift;
1471 if (!num_pages) num_pages = 1; // minimal allocation size for this
1472 return num_pages << vm_page_shift;
1473 }
1474 }
1475
1476 unsigned szone_check_counter = 0;
1477 unsigned szone_check_start = 0;
1478 unsigned szone_check_modulo = 1;
1479
1480 static boolean_t szone_check_all(szone_t *szone, const char *function) {
1481 unsigned index = 0;
1482 SZONE_LOCK(szone);
1483 while (index < szone->num_regions) {
1484 region_t *region = szone->regions + index++;
1485 if (!szone_check_region(szone, region)) {
1486 SZONE_UNLOCK(szone);
1487 szone->debug_flags &= ~ CHECK_REGIONS;
1488 malloc_printf("*** malloc[%d]: Region %d incorrect szone_check_all(%s) counter=%d\n", getpid(), index-1, function, szone_check_counter);
1489 szone_error(szone, "Check: region incorrect", NULL);
1490 return 0;
1491 }
1492 }
1493 index = 0;
1494 while (index < MAX_GRAIN) {
1495 if (! free_list_check(szone, index)) {
1496 SZONE_UNLOCK(szone);
1497 szone->debug_flags &= ~ CHECK_REGIONS;
1498 malloc_printf("*** malloc[%d]: Free list incorrect (grain=%d) szone_check_all(%s) counter=%d\n", getpid(), index, function, szone_check_counter);
1499 szone_error(szone, "Check: free list incorrect", NULL);
1500 return 0;
1501 }
1502 index++;
1503 }
1504 SZONE_UNLOCK(szone);
1505 return 1;
1506 }
1507
1508 static boolean_t szone_check(szone_t *szone) {
1509 if (! (++szone_check_counter % 10000)) {
1510 malloc_printf("At szone_check counter=%d\n", szone_check_counter);
1511 }
1512 if (szone_check_counter < szone_check_start) return 1;
1513 if (szone_check_counter % szone_check_modulo) return 1;
1514 return szone_check_all(szone, "");
1515 }
1516
1517 static kern_return_t szone_ptr_in_use_enumerator(task_t task, void *context, unsigned type_mask, vm_address_t zone_address, memory_reader_t reader, vm_range_recorder_t recorder) {
1518 szone_t *szone;
1519 kern_return_t err;
1520 if (!reader) reader = _szone_default_reader;
1521 // malloc_printf("Enumerator for zone 0x%x\n", zone_address);
1522 err = reader(task, zone_address, sizeof(szone_t), (void **)&szone);
1523 if (err) return err;
1524 // malloc_printf("Small ptrs enumeration for zone 0x%x\n", zone_address);
1525 err = small_in_use_enumerator(task, context, type_mask, (vm_address_t)szone->regions, szone->num_regions, reader, recorder);
1526 if (err) return err;
1527 // malloc_printf("Large ptrs enumeration for zone 0x%x\n", zone_address);
1528 err = large_in_use_enumerator(task, context, type_mask, (vm_address_t)szone->large_entries, szone->num_large_entries, reader, recorder);
1529 if (err) return err;
1530 // malloc_printf("Huge ptrs enumeration for zone 0x%x\n", zone_address);
1531 err = huge_in_use_enumerator(task, context, type_mask, (vm_address_t)szone->huge_entries, szone->num_huge_entries, reader, recorder);
1532 return err;
1533 }
1534
1535 static void szone_print_free_list(szone_t *szone) {
1536 grain_t grain = MAX_GRAIN;
1537 malloc_printf("Free Sizes: ");
1538 while (grain--) {
1539 free_list_t *ptr = szone->free_list[grain];
1540 if (ptr) {
1541 unsigned count = 0;
1542 while (ptr) {
1543 count++;
1544 // malloc_printf("%p ", ptr);
1545 ptr = ptr->next;
1546 }
1547 malloc_printf("%s%d[%d] ", (grain == MAX_GRAIN-1) ? ">=" : "", (grain+1)*QUANTUM, count);
1548 }
1549 }
1550 malloc_printf("\n");
1551 }
1552
1553 static void szone_print(szone_t *szone, boolean_t verbose) {
1554 unsigned info[scalable_zone_info_count];
1555 unsigned index;
1556 unsigned num = 0;
1557 index = LARGE_CACHE_SIZE;
1558 while (index--) if (szone->large_to_deallocate[index].size) num++;
1559 index = 0;
1560 scalable_zone_info((void *)szone, info, scalable_zone_info_count);
1561 malloc_printf("Scalable zone %p: inUse=%d(%dKB) small=%d(%dKB) large=%d(%dKB) to_be_deallocated=%d huge=%d(%dKB) guard_page=%d\n", szone, info[0], info[1] / 1024, info[2], info[3] / 1024, info[4], info[5] / 1024, num, info[6], info[7] / 1024, info[8]);
1562 malloc_printf("%d regions: \n", szone->num_regions);
1563 while (index < szone->num_regions) {
1564 region_t *region = szone->regions + index;
1565 unsigned counts[512];
1566 unsigned ci = 0;
1567 unsigned in_use = 0;
1568 vm_address_t start = REGION_ADDRESS(*region) + QUANTUM;
1569 memset(counts, 0, 512 * sizeof(unsigned));
1570 while (start < REGION_END(*region)) {
1571 msize_t msize_and_free = MSIZE_FLAGS_FOR_PTR(start);
1572 if (!(msize_and_free & THIS_FREE)) {
1573 msize_t msize = msize_and_free & ~PREV_FREE;
1574 if (!msize) break; // last encountered
1575 // block in use
1576 if (msize < 512) counts[msize]++;
1577 start += msize << SHIFT_QUANTUM;
1578 in_use++;
1579 } else {
1580 msize_t msize = msize_and_free & ~THIS_FREE;
1581 // free block
1582 start += msize << SHIFT_QUANTUM;
1583 }
1584 }
1585 malloc_printf("Region [0x%x-0x%x, %dKB] \tIn_use=%d ", REGION_ADDRESS(*region), REGION_END(*region), (int)REGION_SIZE / 1024, in_use);
1586 if (verbose) {
1587 malloc_printf("\n\tSizes in use: ");
1588 while (ci < 512) {
1589 if (counts[ci]) malloc_printf("%d[%d] ", ci << SHIFT_QUANTUM, counts[ci]);
1590 ci++;
1591 }
1592 }
1593 malloc_printf("\n");
1594 index++;
1595 }
1596 if (verbose) szone_print_free_list(szone);
1597 malloc_printf("Free in last zone %d\n", szone->num_bytes_free_in_last_region);
1598 }
1599
1600 static void szone_log(malloc_zone_t *zone, void *log_address) {
1601 szone_t *szone = (void *)zone;
1602 szone->log_address = log_address;
1603 }
1604
1605 static void szone_force_lock(szone_t *szone) {
1606 // malloc_printf("szone_force_lock\n");
1607 SZONE_LOCK(szone);
1608 }
1609
1610 static void szone_force_unlock(szone_t *szone) {
1611 // malloc_printf("szone_force_unlock\n");
1612 SZONE_UNLOCK(szone);
1613 }
1614
1615 static struct malloc_introspection_t szone_introspect = {(void *)szone_ptr_in_use_enumerator, (void *)szone_good_size, (void *)szone_check, (void *)szone_print, szone_log, (void *)szone_force_lock, (void *)szone_force_unlock};
1616
1617 malloc_zone_t *create_scalable_zone(size_t initial_size, unsigned debug_flags) {
1618 szone_t *szone;
1619 vm_address_t addr;
1620 size_t msize;
1621 size_t msize_used = 0;
1622 if (!vm_page_shift) {
1623 unsigned page;
1624 vm_page_shift = 12; // the minimal for page sizes
1625 page = 1 << vm_page_shift;
1626 while (page != vm_page_size) { page += page; vm_page_shift++;};
1627 if (MIN_BLOCK * QUANTUM < sizeof(free_list_t) + PTR_HEADER_SIZE) {
1628 malloc_printf("*** malloc[%d]: inconsistant parameters\n", getpid());
1629 }
1630 }
1631 addr = allocate_pages(NULL, REGION_SIZE, 0, VM_MAKE_TAG(VM_MEMORY_MALLOC));
1632 if (!addr) return NULL;
1633 szone = (void *)(addr + QUANTUM);
1634 msize = (sizeof(szone_t) + PTR_HEADER_SIZE + QUANTUM-1) >> SHIFT_QUANTUM;
1635 MSIZE_FLAGS_FOR_PTR(szone) = msize;
1636 msize_used += msize; szone->num_small_objects++;
1637 szone->basic_zone.size = (void *)szone_size;
1638 szone->basic_zone.malloc = (void *)szone_malloc;
1639 szone->basic_zone.calloc = (void *)szone_calloc;
1640 szone->basic_zone.valloc = (void *)szone_valloc;
1641 szone->basic_zone.free = (void *)szone_free;
1642 szone->basic_zone.realloc = (void *)szone_realloc;
1643 szone->basic_zone.destroy = (void *)szone_destroy;
1644 szone->basic_zone.introspect = &szone_introspect;
1645 LOCK_INIT(szone->lock);
1646 szone->debug_flags = debug_flags;
1647 szone->regions = (void *)((char *)szone + (msize << SHIFT_QUANTUM));
1648 // we always reserve room for a few regions
1649 msize = (sizeof(region_t) * INITIAL_NUM_REGIONS + PTR_HEADER_SIZE + QUANTUM-1) >> SHIFT_QUANTUM;
1650 if (msize < MIN_BLOCK) msize = MIN_BLOCK;
1651 MSIZE_FLAGS_FOR_PTR(szone->regions) = msize;
1652 msize_used += msize; szone->num_small_objects++;
1653 szone->regions[0] = addr;
1654 szone->num_regions = 1;
1655 szone->num_bytes_free_in_last_region = REGION_SIZE - ((msize_used+1) << SHIFT_QUANTUM) + PTR_HEADER_SIZE;
1656 CHECK(szone, __PRETTY_FUNCTION__);
1657 return (malloc_zone_t *)szone;
1658 }
1659
1660 /********* The following is private API for debug and perf tools ************/
1661
1662 void scalable_zone_info(malloc_zone_t *zone, unsigned *info_to_fill, unsigned count) {
1663 szone_t *szone = (void *)zone;
1664 unsigned info[scalable_zone_info_count];
1665 // We do not lock to facilitate debug
1666 info[2] = szone->num_small_objects;
1667 info[3] = szone->num_bytes_in_small_objects;
1668 info[4] = szone->num_large_objects_in_use;
1669 info[5] = szone->num_bytes_in_large_objects;
1670 info[6] = szone->num_huge_entries;
1671 info[7] = szone->num_bytes_in_huge_objects;
1672 info[8] = szone->debug_flags;
1673 info[0] = info[2] + info[4] + info[6];
1674 info[1] = info[3] + info[5] + info[7];
1675 memcpy(info_to_fill, info, sizeof(unsigned)*count);
1676 }
1677