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