]>
Commit | Line | Data |
---|---|---|
2bfd4448 | 1 | /* |
b3962a83 | 2 | * Copyright (c) 2004-2007 Apple Inc. All rights reserved. |
2bfd4448 A |
3 | * |
4 | * @APPLE_LICENSE_HEADER_START@ | |
5 | * | |
2bfd4448 A |
6 | * This file contains Original Code and/or Modifications of Original Code |
7 | * as defined in and that are subject to the Apple Public Source License | |
8 | * Version 2.0 (the 'License'). You may not use this file except in | |
9 | * compliance with the License. Please obtain a copy of the License at | |
10 | * http://www.opensource.apple.com/apsl/ and read it before using this | |
11 | * file. | |
12 | * | |
13 | * The Original Code and all software distributed under the License are | |
14 | * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER | |
15 | * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, | |
16 | * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, | |
17 | * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. | |
18 | * Please see the License for the specific language governing rights and | |
19 | * limitations under the License. | |
20 | * | |
21 | * @APPLE_LICENSE_HEADER_END@ | |
22 | */ | |
2bfd4448 | 23 | |
7257e56c A |
24 | #include "objc-private.h" |
25 | ||
cd5f04f5 A |
26 | #include "objc-config.h" |
27 | #include "objc-auto.h" | |
28 | #include "objc-accessors.h" | |
7af964d1 A |
29 | |
30 | #ifndef OBJC_NO_GC | |
31 | ||
cd5f04f5 A |
32 | #include <stdint.h> |
33 | #include <stdbool.h> | |
34 | #include <fcntl.h> | |
35 | #include <dlfcn.h> | |
36 | #include <mach/mach.h> | |
37 | #include <mach-o/dyld.h> | |
38 | #include <mach-o/nlist.h> | |
39 | #include <sys/types.h> | |
40 | #include <sys/mman.h> | |
41 | #include <libkern/OSAtomic.h> | |
42 | #include <auto_zone.h> | |
43 | ||
44 | #include <Block_private.h> | |
45 | #include <dispatch/private.h> | |
46 | ||
47 | #include "objc-private.h" | |
48 | #include "objc-references.h" | |
49 | #include "maptable.h" | |
50 | #include "message.h" | |
51 | #include "objc-gdb.h" | |
7af964d1 | 52 | |
8972963c | 53 | #if !defined(NDEBUG) && !__OBJC2__ |
cd5f04f5 | 54 | #include "objc-exception.h" |
8972963c | 55 | #endif |
2bfd4448 A |
56 | |
57 | ||
7257e56c | 58 | static auto_zone_t *gc_zone_init(void); |
7af964d1 A |
59 | static void gc_block_init(void); |
60 | static void registeredClassTableInit(void); | |
8972963c | 61 | static BOOL objc_isRegisteredClass(Class candidate); |
2bfd4448 | 62 | |
7257e56c | 63 | int8_t UseGC = -1; |
b3962a83 | 64 | static BOOL WantsMainThreadFinalization = NO; |
2bfd4448 | 65 | |
7257e56c | 66 | auto_zone_t *gc_zone = nil; |
2bfd4448 A |
67 | |
68 | ||
7af964d1 A |
69 | /* Method prototypes */ |
70 | @interface DoesNotExist | |
71 | - (const char *)UTF8String; | |
72 | - (id)description; | |
73 | @end | |
74 | ||
75 | ||
8972963c A |
76 | /*********************************************************************** |
77 | * Break-on-error functions | |
78 | **********************************************************************/ | |
79 | ||
80 | BREAKPOINT_FUNCTION( | |
81 | void objc_assign_ivar_error(id base, ptrdiff_t offset) | |
82 | ); | |
83 | ||
84 | BREAKPOINT_FUNCTION( | |
85 | void objc_assign_global_error(id value, id *slot) | |
86 | ); | |
87 | ||
88 | BREAKPOINT_FUNCTION( | |
89 | void objc_exception_during_finalize_error(void) | |
90 | ); | |
91 | ||
2bfd4448 A |
92 | /*********************************************************************** |
93 | * Utility exports | |
94 | * Called by various libraries. | |
95 | **********************************************************************/ | |
96 | ||
b3962a83 | 97 | OBJC_EXPORT void objc_set_collection_threshold(size_t threshold) { // Old naming |
2bfd4448 | 98 | if (UseGC) { |
b3962a83 | 99 | auto_collection_parameters(gc_zone)->collection_threshold = threshold; |
2bfd4448 A |
100 | } |
101 | } | |
102 | ||
b3962a83 | 103 | OBJC_EXPORT void objc_setCollectionThreshold(size_t threshold) { |
2bfd4448 | 104 | if (UseGC) { |
b3962a83 | 105 | auto_collection_parameters(gc_zone)->collection_threshold = threshold; |
2bfd4448 A |
106 | } |
107 | } | |
108 | ||
b3962a83 A |
109 | void objc_setCollectionRatio(size_t ratio) { |
110 | if (UseGC) { | |
111 | auto_collection_parameters(gc_zone)->full_vs_gen_frequency = ratio; | |
112 | } | |
113 | } | |
114 | ||
115 | void objc_set_collection_ratio(size_t ratio) { // old naming | |
2bfd4448 | 116 | if (UseGC) { |
b3962a83 | 117 | auto_collection_parameters(gc_zone)->full_vs_gen_frequency = ratio; |
2bfd4448 A |
118 | } |
119 | } | |
120 | ||
b3962a83 A |
121 | void objc_finalizeOnMainThread(Class cls) { |
122 | if (UseGC) { | |
123 | WantsMainThreadFinalization = YES; | |
7257e56c | 124 | cls->setShouldFinalizeOnMainThread(); |
b3962a83 A |
125 | } |
126 | } | |
2bfd4448 | 127 | |
7af964d1 A |
128 | // stack based data structure queued if/when there is main-thread-only finalization work TBD |
129 | typedef struct BatchFinalizeBlock { | |
130 | auto_zone_foreach_object_t foreach; | |
131 | auto_zone_cursor_t cursor; | |
132 | size_t cursor_size; | |
133 | volatile BOOL finished; | |
134 | volatile BOOL started; | |
135 | struct BatchFinalizeBlock *next; | |
136 | } BatchFinalizeBlock_t; | |
137 | ||
138 | // The Main Thread Finalization Work Queue Head | |
139 | static struct { | |
140 | pthread_mutex_t mutex; | |
141 | pthread_cond_t condition; | |
142 | BatchFinalizeBlock_t *head; | |
143 | BatchFinalizeBlock_t *tail; | |
144 | } MainThreadWorkQ; | |
145 | ||
b3962a83 A |
146 | |
147 | void objc_startCollectorThread(void) { | |
b3962a83 A |
148 | } |
149 | ||
150 | void objc_start_collector_thread(void) { | |
b3962a83 A |
151 | } |
152 | ||
153 | static void batchFinalizeOnMainThread(void); | |
154 | ||
155 | void objc_collect(unsigned long options) { | |
156 | if (!UseGC) return; | |
157 | BOOL onMainThread = pthread_main_np() ? YES : NO; | |
8972963c A |
158 | |
159 | // while we're here, sneak off and do some finalization work (if any) | |
160 | if (onMainThread) batchFinalizeOnMainThread(); | |
161 | // now on with our normally scheduled programming | |
162 | auto_zone_options_t amode = AUTO_ZONE_COLLECT_NO_OPTIONS; | |
163 | if (!(options & OBJC_COLLECT_IF_NEEDED)) { | |
b3962a83 | 164 | switch (options & 0x3) { |
8972963c A |
165 | case OBJC_RATIO_COLLECTION: amode = AUTO_ZONE_COLLECT_RATIO_COLLECTION; break; |
166 | case OBJC_GENERATIONAL_COLLECTION: amode = AUTO_ZONE_COLLECT_GENERATIONAL_COLLECTION; break; | |
167 | case OBJC_FULL_COLLECTION: amode = AUTO_ZONE_COLLECT_FULL_COLLECTION; break; | |
168 | case OBJC_EXHAUSTIVE_COLLECTION: amode = AUTO_ZONE_COLLECT_EXHAUSTIVE_COLLECTION; break; | |
b3962a83 | 169 | } |
8972963c A |
170 | amode |= AUTO_ZONE_COLLECT_COALESCE; |
171 | amode |= AUTO_ZONE_COLLECT_LOCAL_COLLECTION; | |
b3962a83 | 172 | } |
8972963c A |
173 | if (options & OBJC_WAIT_UNTIL_DONE) { |
174 | __block BOOL done = NO; | |
175 | // If executing on the main thread, use the main thread work queue condition to block, | |
176 | // so main thread finalization can complete. Otherwise, use a thread-local condition. | |
177 | pthread_mutex_t localMutex = PTHREAD_MUTEX_INITIALIZER, *mutex = &localMutex; | |
178 | pthread_cond_t localCondition = PTHREAD_COND_INITIALIZER, *condition = &localCondition; | |
179 | if (onMainThread) { | |
180 | mutex = &MainThreadWorkQ.mutex; | |
181 | condition = &MainThreadWorkQ.condition; | |
182 | } | |
183 | pthread_mutex_lock(mutex); | |
184 | auto_zone_collect_and_notify(gc_zone, amode, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ | |
185 | pthread_mutex_lock(mutex); | |
186 | done = YES; | |
187 | pthread_cond_signal(condition); | |
188 | pthread_mutex_unlock(mutex); | |
189 | }); | |
190 | while (!done) { | |
191 | pthread_cond_wait(condition, mutex); | |
192 | if (onMainThread && MainThreadWorkQ.head) { | |
193 | pthread_mutex_unlock(mutex); | |
194 | batchFinalizeOnMainThread(); | |
195 | pthread_mutex_lock(mutex); | |
196 | } | |
197 | } | |
198 | pthread_mutex_unlock(mutex); | |
199 | } else { | |
200 | auto_zone_collect(gc_zone, amode); | |
b3962a83 | 201 | } |
2bfd4448 A |
202 | } |
203 | ||
204 | ||
b3962a83 | 205 | // USED BY CF & ONE OTHER |
2bfd4448 A |
206 | BOOL objc_isAuto(id object) |
207 | { | |
b3962a83 | 208 | return UseGC && auto_zone_is_valid_pointer(gc_zone, object) != 0; |
2bfd4448 A |
209 | } |
210 | ||
211 | ||
b3962a83 A |
212 | BOOL objc_collectingEnabled(void) |
213 | { | |
214 | return UseGC; | |
215 | } | |
7af964d1 | 216 | |
b3962a83 | 217 | BOOL objc_collecting_enabled(void) // Old naming |
2bfd4448 A |
218 | { |
219 | return UseGC; | |
220 | } | |
221 | ||
8972963c A |
222 | malloc_zone_t *objc_collectableZone(void) { |
223 | return gc_zone; | |
224 | } | |
225 | ||
7af964d1 A |
226 | BOOL objc_dumpHeap(char *filenamebuffer, unsigned long length) { |
227 | static int counter = 0; | |
228 | ++counter; | |
229 | char buffer[1024]; | |
230 | sprintf(buffer, OBJC_HEAP_DUMP_FILENAME_FORMAT, getpid(), counter); | |
231 | if (!_objc_dumpHeap(gc_zone, buffer)) return NO; | |
232 | if (filenamebuffer) { | |
233 | unsigned long blen = strlen(buffer); | |
234 | if (blen < length) | |
235 | strncpy(filenamebuffer, buffer, blen+1); | |
236 | else if (length > 0) | |
237 | filenamebuffer[0] = 0; // give some answer | |
238 | } | |
239 | return YES; | |
240 | } | |
241 | ||
2bfd4448 A |
242 | |
243 | /*********************************************************************** | |
244 | * Memory management. | |
245 | * Called by CF and Foundation. | |
246 | **********************************************************************/ | |
247 | ||
248 | // Allocate an object in the GC zone, with the given number of extra bytes. | |
249 | id objc_allocate_object(Class cls, int extra) | |
250 | { | |
b3962a83 A |
251 | return class_createInstance(cls, extra); |
252 | } | |
253 | ||
254 | ||
255 | /*********************************************************************** | |
256 | * Write barrier implementations, optimized for when GC is known to be on | |
257 | * Called by the write barrier exports only. | |
258 | * These implementations assume GC is on. The exported function must | |
259 | * either perform the check itself or be conditionally stomped at | |
260 | * startup time. | |
261 | **********************************************************************/ | |
262 | ||
cd5f04f5 | 263 | id objc_assign_strongCast_gc(id value, id *slot) { |
5f63f917 A |
264 | if (!auto_zone_set_write_barrier(gc_zone, (void*)slot, value)) { // stores & returns true if slot points into GC allocated memory |
265 | auto_zone_root_write_barrier(gc_zone, slot, value); // always stores | |
266 | } | |
267 | return value; | |
b3962a83 A |
268 | } |
269 | ||
cd5f04f5 | 270 | id objc_assign_global_gc(id value, id *slot) { |
b3962a83 A |
271 | // use explicit root registration. |
272 | if (value && auto_zone_is_valid_pointer(gc_zone, value)) { | |
273 | if (auto_zone_is_finalized(gc_zone, value)) { | |
7257e56c | 274 | _objc_inform("GC: storing an already collected object %p into global memory at %p, break on objc_assign_global_error to debug\n", (void*)value, slot); |
b3962a83 A |
275 | objc_assign_global_error(value, slot); |
276 | } | |
277 | auto_zone_add_root(gc_zone, slot, value); | |
278 | } | |
5f63f917 A |
279 | else |
280 | *slot = value; | |
b3962a83 | 281 | |
5f63f917 | 282 | return value; |
b3962a83 A |
283 | } |
284 | ||
cd5f04f5 | 285 | id objc_assign_threadlocal_gc(id value, id *slot) |
8972963c A |
286 | { |
287 | if (value && auto_zone_is_valid_pointer(gc_zone, value)) { | |
288 | auto_zone_add_root(gc_zone, slot, value); | |
289 | } | |
290 | else { | |
291 | *slot = value; | |
292 | } | |
293 | ||
294 | return value; | |
295 | } | |
b3962a83 | 296 | |
cd5f04f5 | 297 | id objc_assign_ivar_gc(id value, id base, ptrdiff_t offset) |
b3962a83 A |
298 | { |
299 | id *slot = (id*) ((char *)base + offset); | |
300 | ||
301 | if (value) { | |
302 | if (!auto_zone_set_write_barrier(gc_zone, (char *)base + offset, value)) { | |
7257e56c | 303 | _objc_inform("GC: %p + %tu isn't in the auto_zone, break on objc_assign_ivar_error to debug.\n", (void*)base, offset); |
b3962a83 A |
304 | objc_assign_ivar_error(base, offset); |
305 | } | |
306 | } | |
5f63f917 A |
307 | else |
308 | *slot = value; | |
b3962a83 | 309 | |
5f63f917 | 310 | return value; |
2bfd4448 A |
311 | } |
312 | ||
cd5f04f5 | 313 | id objc_assign_strongCast_non_gc(id value, id *slot) { |
8972963c A |
314 | return (*slot = value); |
315 | } | |
316 | ||
cd5f04f5 | 317 | id objc_assign_global_non_gc(id value, id *slot) { |
8972963c A |
318 | return (*slot = value); |
319 | } | |
320 | ||
cd5f04f5 | 321 | id objc_assign_threadlocal_non_gc(id value, id *slot) { |
8972963c A |
322 | return (*slot = value); |
323 | } | |
324 | ||
cd5f04f5 | 325 | id objc_assign_ivar_non_gc(id value, id base, ptrdiff_t offset) { |
8972963c A |
326 | id *slot = (id*) ((char *)base + offset); |
327 | return (*slot = value); | |
328 | } | |
2bfd4448 | 329 | |
7257e56c | 330 | |
2bfd4448 | 331 | /*********************************************************************** |
7257e56c | 332 | * Non-trivial write barriers |
2bfd4448 A |
333 | **********************************************************************/ |
334 | ||
2bfd4448 A |
335 | void *objc_memmove_collectable(void *dst, const void *src, size_t size) |
336 | { | |
337 | if (UseGC) { | |
338 | return auto_zone_write_barrier_memmove(gc_zone, dst, src, size); | |
339 | } else { | |
340 | return memmove(dst, src, size); | |
341 | } | |
342 | } | |
343 | ||
7af964d1 A |
344 | BOOL objc_atomicCompareAndSwapPtr(id predicate, id replacement, volatile id *objectLocation) { |
345 | const BOOL issueMemoryBarrier = NO; | |
346 | if (UseGC) | |
347 | return auto_zone_atomicCompareAndSwapPtr(gc_zone, (void *)predicate, (void *)replacement, (void * volatile *)objectLocation, issueMemoryBarrier); | |
348 | else | |
349 | return OSAtomicCompareAndSwapPtr((void *)predicate, (void *)replacement, (void * volatile *)objectLocation); | |
350 | } | |
351 | ||
352 | BOOL objc_atomicCompareAndSwapPtrBarrier(id predicate, id replacement, volatile id *objectLocation) { | |
353 | const BOOL issueMemoryBarrier = YES; | |
354 | if (UseGC) | |
355 | return auto_zone_atomicCompareAndSwapPtr(gc_zone, (void *)predicate, (void *)replacement, (void * volatile *)objectLocation, issueMemoryBarrier); | |
356 | else | |
357 | return OSAtomicCompareAndSwapPtrBarrier((void *)predicate, (void *)replacement, (void * volatile *)objectLocation); | |
358 | } | |
359 | ||
b3962a83 | 360 | BOOL objc_atomicCompareAndSwapGlobal(id predicate, id replacement, volatile id *objectLocation) { |
7af964d1 A |
361 | const BOOL isGlobal = YES; |
362 | const BOOL issueMemoryBarrier = NO; | |
5f63f917 | 363 | if (UseGC) |
7af964d1 | 364 | return auto_zone_atomicCompareAndSwap(gc_zone, (void *)predicate, (void *)replacement, (void * volatile *)objectLocation, isGlobal, issueMemoryBarrier); |
5f63f917 A |
365 | else |
366 | return OSAtomicCompareAndSwapPtr((void *)predicate, (void *)replacement, (void * volatile *)objectLocation); | |
b3962a83 A |
367 | } |
368 | ||
369 | BOOL objc_atomicCompareAndSwapGlobalBarrier(id predicate, id replacement, volatile id *objectLocation) { | |
7af964d1 A |
370 | const BOOL isGlobal = YES; |
371 | const BOOL issueMemoryBarrier = YES; | |
5f63f917 | 372 | if (UseGC) |
7af964d1 | 373 | return auto_zone_atomicCompareAndSwap(gc_zone, (void *)predicate, (void *)replacement, (void * volatile *)objectLocation, isGlobal, issueMemoryBarrier); |
5f63f917 A |
374 | else |
375 | return OSAtomicCompareAndSwapPtrBarrier((void *)predicate, (void *)replacement, (void * volatile *)objectLocation); | |
b3962a83 A |
376 | } |
377 | ||
378 | BOOL objc_atomicCompareAndSwapInstanceVariable(id predicate, id replacement, volatile id *objectLocation) { | |
7af964d1 A |
379 | const BOOL isGlobal = NO; |
380 | const BOOL issueMemoryBarrier = NO; | |
5f63f917 | 381 | if (UseGC) |
7af964d1 | 382 | return auto_zone_atomicCompareAndSwap(gc_zone, (void *)predicate, (void *)replacement, (void * volatile *)objectLocation, isGlobal, issueMemoryBarrier); |
5f63f917 A |
383 | else |
384 | return OSAtomicCompareAndSwapPtr((void *)predicate, (void *)replacement, (void * volatile *)objectLocation); | |
b3962a83 A |
385 | } |
386 | ||
387 | BOOL objc_atomicCompareAndSwapInstanceVariableBarrier(id predicate, id replacement, volatile id *objectLocation) { | |
7af964d1 A |
388 | const BOOL isGlobal = NO; |
389 | const BOOL issueMemoryBarrier = YES; | |
5f63f917 | 390 | if (UseGC) |
7af964d1 | 391 | return auto_zone_atomicCompareAndSwap(gc_zone, (void *)predicate, (void *)replacement, (void * volatile *)objectLocation, isGlobal, issueMemoryBarrier); |
5f63f917 A |
392 | else |
393 | return OSAtomicCompareAndSwapPtrBarrier((void *)predicate, (void *)replacement, (void * volatile *)objectLocation); | |
394 | } | |
395 | ||
396 | ||
397 | /*********************************************************************** | |
8972963c | 398 | * Weak ivar support |
5f63f917 A |
399 | **********************************************************************/ |
400 | ||
cd5f04f5 | 401 | id objc_read_weak_gc(id *location) { |
8972963c A |
402 | id result = *location; |
403 | if (result) { | |
7257e56c | 404 | result = (id)auto_read_weak_reference(gc_zone, (void **)location); |
7af964d1 | 405 | } |
8972963c | 406 | return result; |
7af964d1 | 407 | } |
b3962a83 | 408 | |
cd5f04f5 | 409 | id objc_read_weak_non_gc(id *location) { |
8972963c A |
410 | return *location; |
411 | } | |
b3962a83 | 412 | |
cd5f04f5 | 413 | id objc_assign_weak_gc(id value, id *location) { |
7257e56c | 414 | auto_assign_weak_reference(gc_zone, value, (const void **)location, nil); |
b3962a83 A |
415 | return value; |
416 | } | |
417 | ||
cd5f04f5 | 418 | id objc_assign_weak_non_gc(id value, id *location) { |
8972963c | 419 | return (*location = value); |
7af964d1 A |
420 | } |
421 | ||
7af964d1 | 422 | |
cd5f04f5 | 423 | void gc_fixup_weakreferences(id newObject, id oldObject) { |
8972963c | 424 | // fix up weak references if any. |
7257e56c | 425 | const unsigned char *weakLayout = (const unsigned char *)class_getWeakIvarLayout(newObject->ISA()); |
8972963c A |
426 | if (weakLayout) { |
427 | void **newPtr = (void **)newObject, **oldPtr = (void **)oldObject; | |
428 | unsigned char byte; | |
429 | while ((byte = *weakLayout++)) { | |
430 | unsigned skips = (byte >> 4); | |
431 | unsigned weaks = (byte & 0x0F); | |
432 | newPtr += skips, oldPtr += skips; | |
433 | while (weaks--) { | |
7257e56c A |
434 | *newPtr = nil; |
435 | auto_assign_weak_reference(gc_zone, auto_read_weak_reference(gc_zone, oldPtr), (const void **)newPtr, nil); | |
8972963c A |
436 | ++newPtr, ++oldPtr; |
437 | } | |
438 | } | |
7af964d1 | 439 | } |
7af964d1 | 440 | } |
2bfd4448 | 441 | |
7257e56c A |
442 | |
443 | /*********************************************************************** | |
444 | * dyld resolver functions for basic GC write barriers | |
445 | * dyld calls the resolver function to bind the symbol. | |
446 | * We return the GC or non-GC variant as appropriate. | |
447 | **********************************************************************/ | |
448 | ||
449 | #define GC_RESOLVER(name) \ | |
450 | OBJC_EXPORT void *name##_resolver(void) __asm__("_" #name); \ | |
451 | void *name##_resolver(void) \ | |
452 | { \ | |
453 | __asm__(".symbol_resolver _" #name); \ | |
454 | if (UseGC) return (void*)name##_gc; \ | |
455 | else return (void*)name##_non_gc; \ | |
456 | } | |
457 | ||
458 | GC_RESOLVER(objc_assign_ivar) | |
459 | GC_RESOLVER(objc_assign_strongCast) | |
460 | GC_RESOLVER(objc_assign_global) | |
461 | GC_RESOLVER(objc_assign_threadlocal) | |
462 | GC_RESOLVER(objc_read_weak) | |
463 | GC_RESOLVER(objc_assign_weak) | |
464 | GC_RESOLVER(objc_getProperty) | |
465 | GC_RESOLVER(objc_setProperty) | |
466 | GC_RESOLVER(objc_getAssociatedObject) | |
467 | GC_RESOLVER(objc_setAssociatedObject) | |
468 | GC_RESOLVER(_object_addExternalReference) | |
469 | GC_RESOLVER(_object_readExternalReference) | |
470 | GC_RESOLVER(_object_removeExternalReference) | |
471 | ||
472 | ||
2bfd4448 A |
473 | /*********************************************************************** |
474 | * Testing tools | |
475 | * Used to isolate resurrection of garbage objects during finalization. | |
476 | **********************************************************************/ | |
477 | BOOL objc_is_finalized(void *ptr) { | |
7257e56c | 478 | if (ptr != nil && UseGC) { |
b3962a83 A |
479 | return auto_zone_is_finalized(gc_zone, ptr); |
480 | } | |
481 | return NO; | |
2bfd4448 A |
482 | } |
483 | ||
484 | ||
b3962a83 | 485 | /*********************************************************************** |
8972963c A |
486 | * Stack clearing. |
487 | * Used by top-level thread loops to reduce false pointers from the stack. | |
b3962a83 | 488 | **********************************************************************/ |
b3962a83 A |
489 | void objc_clear_stack(unsigned long options) { |
490 | if (!UseGC) return; | |
7af964d1 | 491 | auto_zone_clear_stack(gc_zone, 0); |
b3962a83 A |
492 | } |
493 | ||
8972963c | 494 | |
2bfd4448 | 495 | /*********************************************************************** |
b3962a83 | 496 | * Finalization support |
2bfd4448 A |
497 | **********************************************************************/ |
498 | ||
b3962a83 A |
499 | // Finalizer crash debugging |
500 | static void *finalizing_object; | |
7af964d1 A |
501 | |
502 | // finalize a single object without fuss | |
503 | // When there are no main-thread-only classes this is used directly | |
504 | // Otherwise, it is used indirectly by smarter code that knows main-thread-affinity requirements | |
505 | static void finalizeOneObject(void *obj, void *ignored) { | |
b3962a83 | 506 | id object = (id)obj; |
b3962a83 | 507 | finalizing_object = obj; |
8972963c | 508 | |
7257e56c | 509 | Class cls = object->ISA(); |
8972963c | 510 | CRSetCrashLogMessage2(class_getName(cls)); |
2bfd4448 | 511 | |
b3962a83 | 512 | /// call -finalize method. |
cd5f04f5 | 513 | ((void(*)(id, SEL))objc_msgSend)(object, @selector(finalize)); |
8972963c A |
514 | |
515 | // Call C++ destructors. | |
516 | // This would be objc_destructInstance() but for performance. | |
7257e56c | 517 | if (cls->hasCxxDtor()) { |
8972963c A |
518 | object_cxxDestruct(object); |
519 | } | |
2bfd4448 | 520 | |
7257e56c A |
521 | finalizing_object = nil; |
522 | CRSetCrashLogMessage2(nil); | |
2bfd4448 A |
523 | } |
524 | ||
7af964d1 A |
525 | // finalize object only if it is a main-thread-only object. |
526 | // Called only from the main thread. | |
527 | static void finalizeOneMainThreadOnlyObject(void *obj, void *arg) { | |
b3962a83 | 528 | id object = (id)obj; |
7257e56c A |
529 | Class cls = object->ISA(); |
530 | if (cls == nil) { | |
531 | _objc_fatal("object with nil ISA passed to finalizeOneMainThreadOnlyObject: %p\n", obj); | |
b3962a83 | 532 | } |
7257e56c A |
533 | if (cls->shouldFinalizeOnMainThread()) { |
534 | finalizeOneObject(obj, nil); | |
b3962a83 | 535 | } |
2bfd4448 | 536 | } |
2bfd4448 | 537 | |
7af964d1 A |
538 | // finalize one object only if it is not a main-thread-only object |
539 | // called from any other thread than the main thread | |
540 | // Important: if a main-thread-only object is passed, return that fact in the needsMain argument | |
541 | static void finalizeOneAnywhereObject(void *obj, void *needsMain) { | |
b3962a83 | 542 | id object = (id)obj; |
7257e56c A |
543 | Class cls = object->ISA(); |
544 | bool *needsMainThreadWork = (bool *)needsMain; | |
545 | if (cls == nil) { | |
546 | _objc_fatal("object with nil ISA passed to finalizeOneAnywhereObject: %p\n", obj); | |
b3962a83 | 547 | } |
7257e56c A |
548 | if (!cls->shouldFinalizeOnMainThread()) { |
549 | finalizeOneObject(obj, nil); | |
b3962a83 A |
550 | } |
551 | else { | |
7af964d1 | 552 | *needsMainThreadWork = true; |
2bfd4448 | 553 | } |
2bfd4448 A |
554 | } |
555 | ||
2bfd4448 | 556 | |
7af964d1 A |
557 | // Utility workhorse. |
558 | // Set up the expensive @try block and ask the collector to hand the next object to | |
559 | // our finalizeAnObject function. | |
560 | // Track and return a boolean that records whether or not any main thread work is necessary. | |
561 | // (When we know that there are no main thread only objects then the boolean isn't even computed) | |
562 | static bool batchFinalize(auto_zone_t *zone, | |
b3962a83 A |
563 | auto_zone_foreach_object_t foreach, |
564 | auto_zone_cursor_t cursor, | |
565 | size_t cursor_size, | |
7af964d1 | 566 | void (*finalizeAnObject)(void *, void*)) |
b3962a83 | 567 | { |
8972963c A |
568 | #if !defined(NDEBUG) && !__OBJC2__ |
569 | // debug: don't call try/catch before exception handlers are installed | |
cd5f04f5 | 570 | objc_exception_functions_t table = {}; |
8972963c A |
571 | objc_exception_get_functions(&table); |
572 | assert(table.throw_exc); | |
573 | #endif | |
574 | ||
7af964d1 | 575 | bool needsMainThreadWork = false; |
b3962a83 A |
576 | for (;;) { |
577 | @try { | |
7af964d1 | 578 | foreach(cursor, finalizeAnObject, &needsMainThreadWork); |
b3962a83 A |
579 | // non-exceptional return means finalization is complete. |
580 | break; | |
8972963c A |
581 | } |
582 | @catch (id exception) { | |
b3962a83 | 583 | // whoops, note exception, then restart at cursor's position |
b3962a83 A |
584 | _objc_inform("GC: -finalize resulted in an exception (%p) being thrown, break on objc_exception_during_finalize_error to debug\n\t%s", exception, (const char*)[[exception description] UTF8String]); |
585 | objc_exception_during_finalize_error(); | |
8972963c A |
586 | } |
587 | @catch (...) { | |
588 | // whoops, note exception, then restart at cursor's position | |
589 | _objc_inform("GC: -finalize resulted in an exception being thrown, break on objc_exception_during_finalize_error to debug"); | |
590 | objc_exception_during_finalize_error(); | |
b3962a83 | 591 | } |
2bfd4448 | 592 | } |
7af964d1 | 593 | return needsMainThreadWork; |
2bfd4448 | 594 | } |
2bfd4448 | 595 | |
7af964d1 A |
596 | // Called on main thread-only. |
597 | // Pick up work from global queue. | |
598 | // called parasitically by anyone requesting a collection | |
599 | // called explicitly when there is known to be main thread only finalization work | |
600 | // In both cases we are on the main thread | |
601 | // Guard against recursion by something called from a finalizer | |
602 | static void batchFinalizeOnMainThread() { | |
603 | pthread_mutex_lock(&MainThreadWorkQ.mutex); | |
604 | if (!MainThreadWorkQ.head || MainThreadWorkQ.head->started) { | |
605 | // No work or we're already here | |
606 | pthread_mutex_unlock(&MainThreadWorkQ.mutex); | |
2bfd4448 A |
607 | return; |
608 | } | |
7af964d1 A |
609 | while (MainThreadWorkQ.head) { |
610 | BatchFinalizeBlock_t *bfb = MainThreadWorkQ.head; | |
611 | bfb->started = YES; | |
612 | pthread_mutex_unlock(&MainThreadWorkQ.mutex); | |
613 | ||
614 | batchFinalize(gc_zone, bfb->foreach, bfb->cursor, bfb->cursor_size, finalizeOneMainThreadOnlyObject); | |
615 | // signal the collector thread(s) that finalization has finished. | |
616 | pthread_mutex_lock(&MainThreadWorkQ.mutex); | |
617 | bfb->finished = YES; | |
618 | pthread_cond_broadcast(&MainThreadWorkQ.condition); | |
619 | MainThreadWorkQ.head = bfb->next; | |
620 | } | |
7257e56c | 621 | MainThreadWorkQ.tail = nil; |
7af964d1 | 622 | pthread_mutex_unlock(&MainThreadWorkQ.mutex); |
b3962a83 A |
623 | } |
624 | ||
7af964d1 A |
625 | |
626 | // Knowing that we possibly have main thread only work to do, first process everything | |
627 | // that is not main-thread-only. If we discover main thread only work, queue a work block | |
628 | // to the main thread that will do just the main thread only work. Wait for it. | |
629 | // Called from a non main thread. | |
b3962a83 A |
630 | static void batchFinalizeOnTwoThreads(auto_zone_t *zone, |
631 | auto_zone_foreach_object_t foreach, | |
632 | auto_zone_cursor_t cursor, | |
633 | size_t cursor_size) | |
634 | { | |
635 | // First, lets get rid of everything we can on this thread, then ask main thread to help if needed | |
b3962a83 A |
636 | char cursor_copy[cursor_size]; |
637 | memcpy(cursor_copy, cursor, cursor_size); | |
7af964d1 | 638 | bool needsMainThreadFinalization = batchFinalize(zone, foreach, (auto_zone_cursor_t)cursor_copy, cursor_size, finalizeOneAnywhereObject); |
b3962a83 | 639 | |
7af964d1 | 640 | if (! needsMainThreadFinalization) |
b3962a83 | 641 | return; // no help needed |
2bfd4448 | 642 | |
b3962a83 | 643 | // set up the control block. Either our ping of main thread with _callOnMainThread will get to it, or |
7af964d1 A |
644 | // an objc_collect(if_needed) will get to it. Either way, this block will be processed on the main thread. |
645 | BatchFinalizeBlock_t bfb; | |
646 | bfb.foreach = foreach; | |
647 | bfb.cursor = cursor; | |
648 | bfb.cursor_size = cursor_size; | |
649 | bfb.started = NO; | |
650 | bfb.finished = NO; | |
7257e56c | 651 | bfb.next = nil; |
7af964d1 A |
652 | pthread_mutex_lock(&MainThreadWorkQ.mutex); |
653 | if (MainThreadWorkQ.tail) { | |
654 | ||
655 | // link to end so that ordering of finalization is preserved. | |
656 | MainThreadWorkQ.tail->next = &bfb; | |
657 | MainThreadWorkQ.tail = &bfb; | |
658 | } | |
659 | else { | |
660 | MainThreadWorkQ.head = &bfb; | |
661 | MainThreadWorkQ.tail = &bfb; | |
662 | } | |
663 | pthread_mutex_unlock(&MainThreadWorkQ.mutex); | |
2bfd4448 | 664 | |
b3962a83 | 665 | //printf("----->asking main thread to finalize\n"); |
7af964d1 | 666 | dispatch_async(dispatch_get_main_queue(), ^{ batchFinalizeOnMainThread(); }); |
2bfd4448 | 667 | |
b3962a83 | 668 | // wait for the main thread to finish finalizing instances of classes marked CLS_FINALIZE_ON_MAIN_THREAD. |
7af964d1 | 669 | pthread_mutex_lock(&MainThreadWorkQ.mutex); |
8972963c A |
670 | while (!bfb.finished) { |
671 | // the main thread might be blocked waiting for a synchronous collection to complete, so wake it here | |
672 | pthread_cond_signal(&MainThreadWorkQ.condition); | |
673 | pthread_cond_wait(&MainThreadWorkQ.condition, &MainThreadWorkQ.mutex); | |
674 | } | |
7af964d1 | 675 | pthread_mutex_unlock(&MainThreadWorkQ.mutex); |
b3962a83 A |
676 | //printf("<------ main thread finalize done\n"); |
677 | ||
678 | } | |
679 | ||
680 | ||
b3962a83 A |
681 | |
682 | // collector calls this with garbage ready | |
7af964d1 | 683 | // thread collectors, too, so this needs to be thread-safe |
b3962a83 A |
684 | static void BatchInvalidate(auto_zone_t *zone, |
685 | auto_zone_foreach_object_t foreach, | |
686 | auto_zone_cursor_t cursor, | |
687 | size_t cursor_size) | |
2bfd4448 | 688 | { |
b3962a83 A |
689 | if (pthread_main_np() || !WantsMainThreadFinalization) { |
690 | // Collect all objects. We're either pre-multithreaded on main thread or we're on the collector thread | |
691 | // but no main-thread-only objects have been allocated. | |
692 | batchFinalize(zone, foreach, cursor, cursor_size, finalizeOneObject); | |
693 | } | |
694 | else { | |
695 | // We're on the dedicated thread. Collect some on main thread, the rest here. | |
696 | batchFinalizeOnTwoThreads(zone, foreach, cursor, cursor_size); | |
2bfd4448 | 697 | } |
b3962a83 A |
698 | |
699 | } | |
700 | ||
7af964d1 A |
701 | |
702 | /* | |
703 | * Zombie support | |
704 | * Collector calls into this system when it finds resurrected objects. | |
705 | * This keeps them pitifully alive and leaked, even if they reference garbage. | |
706 | */ | |
707 | ||
b3962a83 A |
708 | // idea: keep a side table mapping resurrected object pointers to their original Class, so we don't |
709 | // need to smash anything. alternatively, could use associative references to track against a secondary | |
710 | // object with information about the resurrection, such as a stack crawl, etc. | |
711 | ||
712 | static Class _NSResurrectedObjectClass; | |
7257e56c | 713 | static NXMapTable *_NSResurrectedObjectMap = nil; |
7af964d1 | 714 | static pthread_mutex_t _NSResurrectedObjectLock = PTHREAD_MUTEX_INITIALIZER; |
b3962a83 A |
715 | |
716 | static Class resurrectedObjectOriginalClass(id object) { | |
717 | Class originalClass; | |
7af964d1 | 718 | pthread_mutex_lock(&_NSResurrectedObjectLock); |
b3962a83 | 719 | originalClass = (Class) NXMapGet(_NSResurrectedObjectMap, object); |
7af964d1 | 720 | pthread_mutex_unlock(&_NSResurrectedObjectLock); |
b3962a83 A |
721 | return originalClass; |
722 | } | |
723 | ||
724 | static id _NSResurrectedObject_classMethod(id self, SEL selector) { return self; } | |
725 | ||
726 | static id _NSResurrectedObject_instanceMethod(id self, SEL name) { | |
7257e56c | 727 | _objc_inform("**resurrected** object %p of class %s being sent message '%s'\n", (void*)self, class_getName(resurrectedObjectOriginalClass(self)), sel_getName(name)); |
b3962a83 | 728 | return self; |
2bfd4448 A |
729 | } |
730 | ||
b3962a83 A |
731 | static void _NSResurrectedObject_finalize(id self, SEL _cmd) { |
732 | Class originalClass; | |
7af964d1 | 733 | pthread_mutex_lock(&_NSResurrectedObjectLock); |
b3962a83 | 734 | originalClass = (Class) NXMapRemove(_NSResurrectedObjectMap, self); |
7af964d1 | 735 | pthread_mutex_unlock(&_NSResurrectedObjectLock); |
7257e56c | 736 | if (originalClass) _objc_inform("**resurrected** object %p of class %s being finalized\n", (void*)self, class_getName(originalClass)); |
cd5f04f5 | 737 | _objc_rootFinalize(self); |
2bfd4448 | 738 | } |
2bfd4448 | 739 | |
b3962a83 A |
740 | static BOOL _NSResurrectedObject_resolveInstanceMethod(id self, SEL _cmd, SEL name) { |
741 | class_addMethod((Class)self, name, (IMP)_NSResurrectedObject_instanceMethod, "@@:"); | |
742 | return YES; | |
2bfd4448 | 743 | } |
b3962a83 A |
744 | |
745 | static BOOL _NSResurrectedObject_resolveClassMethod(id self, SEL _cmd, SEL name) { | |
7257e56c | 746 | class_addMethod(self->ISA(), name, (IMP)_NSResurrectedObject_classMethod, "@@:"); |
b3962a83 | 747 | return YES; |
2bfd4448 | 748 | } |
2bfd4448 | 749 | |
b3962a83 A |
750 | static void _NSResurrectedObject_initialize() { |
751 | _NSResurrectedObjectMap = NXCreateMapTable(NXPtrValueMapPrototype, 128); | |
752 | _NSResurrectedObjectClass = objc_allocateClassPair(objc_getClass("NSObject"), "_NSResurrectedObject", 0); | |
753 | class_addMethod(_NSResurrectedObjectClass, @selector(finalize), (IMP)_NSResurrectedObject_finalize, "v@:"); | |
7257e56c | 754 | Class metaClass = _NSResurrectedObjectClass->ISA(); |
b3962a83 A |
755 | class_addMethod(metaClass, @selector(resolveInstanceMethod:), (IMP)_NSResurrectedObject_resolveInstanceMethod, "c@::"); |
756 | class_addMethod(metaClass, @selector(resolveClassMethod:), (IMP)_NSResurrectedObject_resolveClassMethod, "c@::"); | |
757 | objc_registerClassPair(_NSResurrectedObjectClass); | |
758 | } | |
2bfd4448 A |
759 | |
760 | static void resurrectZombie(auto_zone_t *zone, void *ptr) { | |
b3962a83 | 761 | id object = (id) ptr; |
7257e56c | 762 | Class cls = object->ISA(); |
b3962a83 A |
763 | if (cls != _NSResurrectedObjectClass) { |
764 | // remember the original class for this instance. | |
7af964d1 | 765 | pthread_mutex_lock(&_NSResurrectedObjectLock); |
b3962a83 | 766 | NXMapInsert(_NSResurrectedObjectMap, ptr, cls); |
7af964d1 | 767 | pthread_mutex_unlock(&_NSResurrectedObjectLock); |
8972963c | 768 | object_setClass(object, _NSResurrectedObjectClass); |
2bfd4448 A |
769 | } |
770 | } | |
771 | ||
772 | /*********************************************************************** | |
b3962a83 | 773 | * Pretty printing support |
2bfd4448 A |
774 | * For development purposes. |
775 | **********************************************************************/ | |
776 | ||
2bfd4448 | 777 | |
b3962a83 | 778 | static char *name_for_address(auto_zone_t *zone, vm_address_t base, vm_address_t offset, int withRetainCount); |
2bfd4448 | 779 | |
b3962a83 | 780 | static char* objc_name_for_address(auto_zone_t *zone, vm_address_t base, vm_address_t offset) |
2bfd4448 | 781 | { |
b3962a83 | 782 | return name_for_address(zone, base, offset, false); |
2bfd4448 A |
783 | } |
784 | ||
8972963c A |
785 | static const char* objc_name_for_object(auto_zone_t *zone, void *object) { |
786 | Class cls = *(Class *)object; | |
787 | if (!objc_isRegisteredClass(cls)) return ""; | |
788 | return class_getName(cls); | |
789 | } | |
790 | ||
b3962a83 A |
791 | /*********************************************************************** |
792 | * Collection support | |
793 | **********************************************************************/ | |
2bfd4448 | 794 | |
7af964d1 A |
795 | static BOOL objc_isRegisteredClass(Class candidate); |
796 | ||
8972963c A |
797 | static const unsigned char *objc_layout_for_address(auto_zone_t *zone, void *address) { |
798 | id object = (id)address; | |
7257e56c A |
799 | volatile void *clsptr = (void*)object->ISA(); |
800 | Class cls = (Class)clsptr; | |
801 | return objc_isRegisteredClass(cls) ? _object_getIvarLayout(cls, object) : nil; | |
2bfd4448 A |
802 | } |
803 | ||
8972963c A |
804 | static const unsigned char *objc_weak_layout_for_address(auto_zone_t *zone, void *address) { |
805 | id object = (id)address; | |
7257e56c A |
806 | volatile void *clsptr = (void*)object->ISA(); |
807 | Class cls = (Class)clsptr; | |
808 | return objc_isRegisteredClass(cls) ? class_getWeakIvarLayout(cls) : nil; | |
2bfd4448 A |
809 | } |
810 | ||
cd5f04f5 | 811 | void gc_register_datasegment(uintptr_t base, size_t size) { |
7af964d1 A |
812 | auto_zone_register_datasegment(gc_zone, (void*)base, size); |
813 | } | |
2bfd4448 | 814 | |
cd5f04f5 | 815 | void gc_unregister_datasegment(uintptr_t base, size_t size) { |
7af964d1 A |
816 | auto_zone_unregister_datasegment(gc_zone, (void*)base, size); |
817 | } | |
2bfd4448 | 818 | |
2bfd4448 | 819 | |
7af964d1 A |
820 | /*********************************************************************** |
821 | * Initialization | |
822 | **********************************************************************/ | |
b3962a83 | 823 | |
7af964d1 | 824 | static void objc_will_grow(auto_zone_t *zone, auto_heap_growth_info_t info) { |
8972963c A |
825 | if (auto_zone_is_collecting(gc_zone)) { |
826 | ; | |
827 | } | |
828 | else { | |
829 | auto_zone_collect(gc_zone, AUTO_ZONE_COLLECT_COALESCE|AUTO_ZONE_COLLECT_RATIO_COLLECTION); | |
2bfd4448 A |
830 | } |
831 | } | |
832 | ||
833 | ||
7257e56c | 834 | static auto_zone_t *gc_zone_init(void) |
2bfd4448 A |
835 | { |
836 | auto_zone_t *result; | |
8972963c A |
837 | static int didOnce = 0; |
838 | if (!didOnce) { | |
839 | didOnce = 1; | |
840 | ||
841 | // initialize the batch finalization queue | |
7257e56c A |
842 | MainThreadWorkQ.head = nil; |
843 | MainThreadWorkQ.tail = nil; | |
844 | pthread_mutex_init(&MainThreadWorkQ.mutex, nil); | |
845 | pthread_cond_init(&MainThreadWorkQ.condition, nil); | |
8972963c A |
846 | } |
847 | ||
b3962a83 | 848 | result = auto_zone_create("auto_zone"); |
2bfd4448 | 849 | |
7257e56c | 850 | auto_zone_disable_compaction(result); |
8972963c | 851 | |
2bfd4448 A |
852 | auto_collection_control_t *control = auto_collection_parameters(result); |
853 | ||
854 | // set up the magic control parameters | |
b3962a83 A |
855 | control->batch_invalidate = BatchInvalidate; |
856 | control->will_grow = objc_will_grow; | |
2bfd4448 | 857 | control->resurrect = resurrectZombie; |
b3962a83 A |
858 | control->layout_for_address = objc_layout_for_address; |
859 | control->weak_layout_for_address = objc_weak_layout_for_address; | |
2bfd4448 | 860 | control->name_for_address = objc_name_for_address; |
8972963c A |
861 | |
862 | if (control->version >= sizeof(auto_collection_control_t)) { | |
863 | control->name_for_object = objc_name_for_object; | |
864 | } | |
b3962a83 | 865 | |
2bfd4448 A |
866 | return result; |
867 | } | |
868 | ||
869 | ||
7af964d1 A |
870 | /* should be defined in /usr/local/include/libdispatch_private.h. */ |
871 | extern void (*dispatch_begin_thread_4GC)(void); | |
872 | extern void (*dispatch_end_thread_4GC)(void); | |
873 | ||
8972963c A |
874 | static void objc_reapThreadLocalBlocks() |
875 | { | |
876 | if (UseGC) auto_zone_reap_all_local_blocks(gc_zone); | |
877 | } | |
878 | ||
7af964d1 A |
879 | void objc_registerThreadWithCollector() |
880 | { | |
881 | if (UseGC) auto_zone_register_thread(gc_zone); | |
882 | } | |
883 | ||
884 | void objc_unregisterThreadWithCollector() | |
885 | { | |
886 | if (UseGC) auto_zone_unregister_thread(gc_zone); | |
887 | } | |
888 | ||
889 | void objc_assertRegisteredThreadWithCollector() | |
890 | { | |
891 | if (UseGC) auto_zone_assert_thread_registered(gc_zone); | |
892 | } | |
893 | ||
894 | // Always called by _objcInit, even if GC is off. | |
7257e56c | 895 | void gc_init(BOOL wantsGC) |
7af964d1 | 896 | { |
7257e56c | 897 | assert(UseGC == -1); |
8972963c | 898 | UseGC = wantsGC; |
7af964d1 A |
899 | |
900 | if (PrintGC) { | |
8972963c | 901 | _objc_inform("GC: is %s", wantsGC ? "ON" : "OFF"); |
7af964d1 A |
902 | } |
903 | ||
904 | if (UseGC) { | |
7af964d1 | 905 | // Set up the GC zone |
7257e56c | 906 | gc_zone = gc_zone_init(); |
8972963c A |
907 | |
908 | // tell libdispatch to register its threads with the GC. | |
909 | dispatch_begin_thread_4GC = objc_registerThreadWithCollector; | |
910 | dispatch_end_thread_4GC = objc_reapThreadLocalBlocks; | |
7af964d1 | 911 | |
7af964d1 A |
912 | // set up the registered classes list |
913 | registeredClassTableInit(); | |
914 | ||
915 | // tell Blocks to use collectable memory. CF will cook up the classes separately. | |
916 | gc_block_init(); | |
cd5f04f5 A |
917 | |
918 | // Add GC state to crash log reports | |
919 | _objc_inform_on_crash("garbage collection is ON"); | |
7af964d1 A |
920 | } |
921 | } | |
922 | ||
923 | ||
cd5f04f5 A |
924 | // Called by NSObject +load to perform late GC setup |
925 | // This work must wait until after all of libSystem initializes. | |
926 | void gc_init2(void) | |
2bfd4448 | 927 | { |
cd5f04f5 | 928 | assert(UseGC); |
2bfd4448 | 929 | |
b3962a83 A |
930 | // create the _NSResurrectedObject class used to track resurrections. |
931 | _NSResurrectedObject_initialize(); | |
932 | ||
cd5f04f5 A |
933 | // tell libauto to set up its dispatch queues |
934 | auto_collect_multithreaded(gc_zone); | |
935 | } | |
936 | ||
937 | // Called by Foundation. | |
938 | // This function used to initialize NSObject stuff, but now does nothing. | |
939 | malloc_zone_t *objc_collect_init(int (*callback)(void) __unused) | |
940 | { | |
2bfd4448 A |
941 | return (malloc_zone_t *)gc_zone; |
942 | } | |
943 | ||
7af964d1 A |
944 | /* |
945 | * Support routines for the Block implementation | |
946 | */ | |
947 | ||
2bfd4448 | 948 | |
7af964d1 A |
949 | // The Block runtime now needs to sometimes allocate a Block that is an Object - namely |
950 | // when it neesd to have a finalizer which, for now, is only if there are C++ destructors | |
951 | // in the helper function. Hence the isObject parameter. | |
952 | // Under GC a -copy message should allocate a refcount 0 block, ergo the isOne parameter. | |
953 | static void *block_gc_alloc5(const unsigned long size, const bool isOne, const bool isObject) { | |
954 | auto_memory_type_t type = isObject ? (AUTO_OBJECT|AUTO_MEMORY_SCANNED) : AUTO_MEMORY_SCANNED; | |
955 | return auto_zone_allocate_object(gc_zone, size, type, isOne, false); | |
956 | } | |
957 | ||
958 | // The Blocks runtime keeps track of everything above 1 and so it only calls | |
959 | // up to the collector to tell it about the 0->1 transition and then the 1->0 transition | |
960 | static void block_gc_setHasRefcount(const void *block, const bool hasRefcount) { | |
961 | if (hasRefcount) | |
962 | auto_zone_retain(gc_zone, (void *)block); | |
963 | else | |
964 | auto_zone_release(gc_zone, (void *)block); | |
965 | } | |
966 | ||
967 | static void block_gc_memmove(void *dst, void *src, unsigned long size) { | |
968 | auto_zone_write_barrier_memmove(gc_zone, dst, src, (size_t)size); | |
969 | } | |
2bfd4448 | 970 | |
7af964d1 | 971 | static void gc_block_init(void) { |
7af964d1 | 972 | _Block_use_GC( |
8972963c A |
973 | block_gc_alloc5, |
974 | block_gc_setHasRefcount, | |
975 | (void (*)(void *, void **))objc_assign_strongCast_gc, | |
976 | (void (*)(const void *, void *))objc_assign_weak, | |
977 | block_gc_memmove | |
7af964d1 A |
978 | ); |
979 | } | |
2bfd4448 A |
980 | |
981 | ||
982 | /*********************************************************************** | |
7af964d1 A |
983 | * Track classes. |
984 | * In addition to the global class hashtable (set) indexed by name, we | |
985 | * also keep one based purely by pointer when running under Garbage Collection. | |
986 | * This allows the background collector to race against objects recycled from TLC. | |
987 | * Specifically, the background collector can read the admin byte and see that | |
988 | * a thread local object is an object, get scheduled out, and the TLC recovers it, | |
989 | * linking it into the cache, then the background collector reads the isa field and | |
990 | * finds linkage info. By qualifying all isa fields read we avoid this. | |
2bfd4448 A |
991 | **********************************************************************/ |
992 | ||
7af964d1 A |
993 | // This is a self-contained hash table of all classes. The first two elements contain the (size-1) and count. |
994 | static volatile Class *AllClasses = nil; | |
995 | ||
996 | #define SHIFT 3 | |
997 | #define INITIALSIZE 512 | |
cd5f04f5 | 998 | #define REMOVED ~0ul |
7af964d1 A |
999 | |
1000 | // Allocate the side table. | |
1001 | static void registeredClassTableInit() { | |
1002 | assert(UseGC); | |
1003 | // allocate a collectable (refcount 0) zeroed hunk of unscanned memory | |
8972963c | 1004 | uintptr_t *table = (uintptr_t *)auto_zone_allocate_object(gc_zone, INITIALSIZE*sizeof(void *), AUTO_MEMORY_UNSCANNED, true, true); |
7af964d1 A |
1005 | // set initial capacity (as mask) |
1006 | table[0] = INITIALSIZE - 1; | |
1007 | // set initial count | |
1008 | table[1] = 0; | |
8972963c | 1009 | AllClasses = (Class *)table; |
7af964d1 A |
1010 | } |
1011 | ||
1012 | // Verify that a particular pointer is to a class. | |
1013 | // Safe from any thread anytime | |
1014 | static BOOL objc_isRegisteredClass(Class candidate) { | |
1015 | assert(UseGC); | |
8972963c A |
1016 | // nil is never a valid ISA. |
1017 | if (candidate == nil) return NO; | |
7af964d1 A |
1018 | // We don't care about a race with another thread adding a class to which we randomly might have a pointer |
1019 | // Get local copy of classes so that we're immune from updates. | |
1020 | // We keep the size of the list as the first element so there is no race as the list & size get updated. | |
1021 | uintptr_t *allClasses = (uintptr_t *)AllClasses; | |
1022 | // Slot 0 is always the size of the list in log 2 masked terms (e.g. size - 1) where size is always power of 2 | |
1023 | // Slot 1 is count | |
1024 | uintptr_t slot = (((uintptr_t)candidate) >> SHIFT) & allClasses[0]; | |
1025 | // avoid slot 0 and 1 | |
1026 | if (slot < 2) slot = 2; | |
1027 | for(;;) { | |
1028 | long int slotValue = allClasses[slot]; | |
1029 | if (slotValue == (long int)candidate) { | |
1030 | return YES; | |
1031 | } | |
1032 | if (slotValue == 0) { | |
1033 | return NO; | |
1034 | } | |
1035 | ++slot; | |
1036 | if (slot > allClasses[0]) | |
1037 | slot = 2; // skip size, count | |
1038 | } | |
1039 | } | |
1040 | ||
1041 | // Utility used when growing | |
1042 | // Assumes lock held | |
1043 | static void addClassHelper(uintptr_t *table, uintptr_t candidate) { | |
1044 | uintptr_t slot = (((long int)candidate) >> SHIFT) & table[0]; | |
1045 | if (slot < 2) slot = 2; | |
1046 | for(;;) { | |
1047 | uintptr_t slotValue = table[slot]; | |
1048 | if (slotValue == 0) { | |
1049 | table[slot] = candidate; | |
1050 | ++table[1]; | |
1051 | return; | |
1052 | } | |
1053 | ++slot; | |
1054 | if (slot > table[0]) | |
1055 | slot = 2; // skip size, count | |
1056 | } | |
2bfd4448 A |
1057 | } |
1058 | ||
7af964d1 | 1059 | // lock held by callers |
7af964d1 A |
1060 | void objc_addRegisteredClass(Class candidate) { |
1061 | if (!UseGC) return; | |
1062 | uintptr_t *table = (uintptr_t *)AllClasses; | |
1063 | // Slot 0 is always the size of the list in log 2 masked terms (e.g. size - 1) where size is always power of 2 | |
1064 | // Slot 1 is count - always non-zero | |
1065 | uintptr_t slot = (((long int)candidate) >> SHIFT) & table[0]; | |
1066 | if (slot < 2) slot = 2; | |
1067 | for(;;) { | |
1068 | uintptr_t slotValue = table[slot]; | |
1069 | assert(slotValue != (uintptr_t)candidate); | |
1070 | if (slotValue == REMOVED) { | |
1071 | table[slot] = (long)candidate; | |
1072 | return; | |
1073 | } | |
1074 | else if (slotValue == 0) { | |
1075 | table[slot] = (long)candidate; | |
1076 | if (2*++table[1] > table[0]) { // add to count; check if we cross 50% utilization | |
1077 | // grow | |
1078 | uintptr_t oldSize = table[0]+1; | |
8972963c | 1079 | uintptr_t *newTable = (uintptr_t *)auto_zone_allocate_object(gc_zone, oldSize*2*sizeof(void *), AUTO_MEMORY_UNSCANNED, true, true); |
7af964d1 A |
1080 | uintptr_t i; |
1081 | newTable[0] = 2*oldSize - 1; | |
1082 | newTable[1] = 0; | |
1083 | for (i = 2; i < oldSize; ++i) { | |
1084 | if (table[i] && table[i] != REMOVED) | |
1085 | addClassHelper(newTable, table[i]); | |
1086 | } | |
8972963c A |
1087 | AllClasses = (Class *)newTable; |
1088 | // let the old table be collected when other threads are no longer reading it. | |
1089 | auto_zone_release(gc_zone, (void *)table); | |
7af964d1 A |
1090 | } |
1091 | return; | |
1092 | } | |
1093 | ++slot; | |
1094 | if (slot > table[0]) | |
1095 | slot = 2; // skip size, count | |
1096 | } | |
1097 | } | |
1098 | ||
1099 | // lock held by callers | |
7af964d1 A |
1100 | void objc_removeRegisteredClass(Class candidate) { |
1101 | if (!UseGC) return; | |
1102 | uintptr_t *table = (uintptr_t *)AllClasses; | |
1103 | // Slot 0 is always the size of the list in log 2 masked terms (e.g. size - 1) where size is always power of 2 | |
1104 | // Slot 1 is count - always non-zero | |
1105 | uintptr_t slot = (((uintptr_t)candidate) >> SHIFT) & table[0]; | |
1106 | if (slot < 2) slot = 2; | |
1107 | for(;;) { | |
1108 | uintptr_t slotValue = table[slot]; | |
1109 | if (slotValue == (uintptr_t)candidate) { | |
1110 | table[slot] = REMOVED; // if next slot == 0 we could set to 0 here and decr count | |
1111 | return; | |
1112 | } | |
1113 | assert(slotValue != 0); | |
1114 | ++slot; | |
1115 | if (slot > table[0]) | |
1116 | slot = 2; // skip size, count | |
1117 | } | |
1118 | } | |
1119 | ||
1120 | ||
1121 | /*********************************************************************** | |
1122 | * Debugging - support for smart printouts when errors occur | |
1123 | **********************************************************************/ | |
1124 | ||
1125 | ||
2bfd4448 A |
1126 | static malloc_zone_t *objc_debug_zone(void) |
1127 | { | |
7257e56c | 1128 | static malloc_zone_t *z = nil; |
2bfd4448 | 1129 | if (!z) { |
8070259c | 1130 | z = malloc_create_zone(PAGE_MAX_SIZE, 0); |
2bfd4448 A |
1131 | malloc_set_zone_name(z, "objc-auto debug"); |
1132 | } | |
1133 | return z; | |
1134 | } | |
1135 | ||
b3962a83 | 1136 | static char *_malloc_append_unsigned(uintptr_t value, unsigned base, char *head) { |
2bfd4448 A |
1137 | if (!value) { |
1138 | head[0] = '0'; | |
1139 | } else { | |
1140 | if (value >= base) head = _malloc_append_unsigned(value / base, base, head); | |
1141 | value = value % base; | |
1142 | head[0] = (value < 10) ? '0' + value : 'a' + value - 10; | |
1143 | } | |
1144 | return head+1; | |
1145 | } | |
1146 | ||
7af964d1 | 1147 | static void strlcati(char *str, uintptr_t value, size_t bufSize) |
2bfd4448 | 1148 | { |
7af964d1 A |
1149 | if ( (bufSize - strlen(str)) < 30) |
1150 | return; | |
2bfd4448 A |
1151 | str = _malloc_append_unsigned(value, 10, str + strlen(str)); |
1152 | str[0] = '\0'; | |
1153 | } | |
1154 | ||
2bfd4448 | 1155 | |
b3962a83 | 1156 | static Ivar ivar_for_offset(Class cls, vm_address_t offset) |
2bfd4448 | 1157 | { |
cd5f04f5 A |
1158 | unsigned i; |
1159 | vm_address_t ivar_offset; | |
b3962a83 A |
1160 | Ivar super_ivar, result; |
1161 | Ivar *ivars; | |
1162 | unsigned int ivar_count; | |
2bfd4448 | 1163 | |
7257e56c | 1164 | if (!cls) return nil; |
2bfd4448 A |
1165 | |
1166 | // scan base classes FIRST | |
7257e56c | 1167 | super_ivar = ivar_for_offset(cls->superclass, offset); |
2bfd4448 A |
1168 | // result is best-effort; our ivars may be closer |
1169 | ||
b3962a83 A |
1170 | ivars = class_copyIvarList(cls, &ivar_count); |
1171 | if (ivars && ivar_count) { | |
1172 | // Try our first ivar. If it's too big, use super's best ivar. | |
7af964d1 | 1173 | // (lose 64-bit precision) |
b3962a83 A |
1174 | ivar_offset = ivar_getOffset(ivars[0]); |
1175 | if (ivar_offset > offset) result = super_ivar; | |
1176 | else if (ivar_offset == offset) result = ivars[0]; | |
7257e56c | 1177 | else result = nil; |
b3962a83 A |
1178 | |
1179 | // Try our other ivars. If any is too big, use the previous. | |
7257e56c | 1180 | for (i = 1; result == nil && i < ivar_count; i++) { |
b3962a83 A |
1181 | ivar_offset = ivar_getOffset(ivars[i]); |
1182 | if (ivar_offset == offset) { | |
1183 | result = ivars[i]; | |
1184 | } else if (ivar_offset > offset) { | |
1185 | result = ivars[i - 1]; | |
1186 | } | |
2bfd4448 | 1187 | } |
2bfd4448 | 1188 | |
b3962a83 | 1189 | // Found nothing. Return our last ivar. |
7257e56c | 1190 | if (result == nil) |
b3962a83 A |
1191 | result = ivars[ivar_count - 1]; |
1192 | ||
1193 | free(ivars); | |
1194 | } else { | |
1195 | result = super_ivar; | |
1196 | } | |
1197 | ||
1198 | return result; | |
2bfd4448 A |
1199 | } |
1200 | ||
7af964d1 | 1201 | static void append_ivar_at_offset(char *buf, Class cls, vm_address_t offset, size_t bufSize) |
2bfd4448 | 1202 | { |
7257e56c | 1203 | Ivar ivar = nil; |
2bfd4448 A |
1204 | |
1205 | if (offset == 0) return; // don't bother with isa | |
b3962a83 | 1206 | if (offset >= class_getInstanceSize(cls)) { |
7af964d1 A |
1207 | strlcat(buf, ".<extra>+", bufSize); |
1208 | strlcati(buf, offset, bufSize); | |
2bfd4448 A |
1209 | return; |
1210 | } | |
1211 | ||
1212 | ivar = ivar_for_offset(cls, offset); | |
1213 | if (!ivar) { | |
7af964d1 | 1214 | strlcat(buf, ".<?>", bufSize); |
2bfd4448 A |
1215 | return; |
1216 | } | |
1217 | ||
1218 | // fixme doesn't handle structs etc. | |
1219 | ||
7af964d1 | 1220 | strlcat(buf, ".", bufSize); |
b3962a83 | 1221 | const char *ivar_name = ivar_getName(ivar); |
7af964d1 A |
1222 | if (ivar_name) strlcat(buf, ivar_name, bufSize); |
1223 | else strlcat(buf, "<anonymous ivar>", bufSize); | |
2bfd4448 | 1224 | |
b3962a83 | 1225 | offset -= ivar_getOffset(ivar); |
2bfd4448 | 1226 | if (offset > 0) { |
7af964d1 A |
1227 | strlcat(buf, "+", bufSize); |
1228 | strlcati(buf, offset, bufSize); | |
2bfd4448 A |
1229 | } |
1230 | } | |
1231 | ||
1232 | ||
1233 | static const char *cf_class_for_object(void *cfobj) | |
1234 | { | |
1235 | // ick - we don't link against CF anymore | |
1236 | ||
7257e56c A |
1237 | struct fake_cfclass { |
1238 | size_t version; | |
1239 | const char *className; | |
1240 | // don't care about the rest | |
1241 | }; | |
1242 | ||
b3962a83 A |
1243 | const char *result; |
1244 | void *dlh; | |
1245 | size_t (*CFGetTypeID)(void *); | |
7257e56c | 1246 | fake_cfclass * (*_CFRuntimeGetClassWithTypeID)(size_t); |
b3962a83 A |
1247 | |
1248 | result = "anonymous_NSCFType"; | |
2bfd4448 | 1249 | |
b3962a83 A |
1250 | dlh = dlopen("/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation", RTLD_LAZY | RTLD_NOLOAD | RTLD_FIRST); |
1251 | if (!dlh) return result; | |
2bfd4448 | 1252 | |
b3962a83 | 1253 | CFGetTypeID = (size_t(*)(void*)) dlsym(dlh, "CFGetTypeID"); |
7257e56c | 1254 | _CFRuntimeGetClassWithTypeID = (fake_cfclass*(*)(size_t)) dlsym(dlh, "_CFRuntimeGetClassWithTypeID"); |
b3962a83 A |
1255 | |
1256 | if (CFGetTypeID && _CFRuntimeGetClassWithTypeID) { | |
7257e56c A |
1257 | size_t cfid = (*CFGetTypeID)(cfobj); |
1258 | result = (*_CFRuntimeGetClassWithTypeID)(cfid)->className; | |
b3962a83 | 1259 | } |
2bfd4448 | 1260 | |
b3962a83 A |
1261 | dlclose(dlh); |
1262 | return result; | |
2bfd4448 A |
1263 | } |
1264 | ||
1265 | ||
1266 | static char *name_for_address(auto_zone_t *zone, vm_address_t base, vm_address_t offset, int withRetainCount) | |
1267 | { | |
1268 | #define APPEND_SIZE(s) \ | |
7af964d1 A |
1269 | strlcat(buf, "[", sizeof(buf)); \ |
1270 | strlcati(buf, s, sizeof(buf)); \ | |
1271 | strlcat(buf, "]", sizeof(buf)); | |
2bfd4448 | 1272 | |
7af964d1 | 1273 | char buf[1500]; |
2bfd4448 A |
1274 | char *result; |
1275 | ||
1276 | buf[0] = '\0'; | |
1277 | ||
b3962a83 | 1278 | size_t size = |
7af964d1 | 1279 | auto_zone_size(zone, (void *)base); |
2bfd4448 | 1280 | auto_memory_type_t type = size ? |
7af964d1 | 1281 | auto_zone_get_layout_type(zone, (void *)base) : AUTO_TYPE_UNKNOWN; |
2bfd4448 | 1282 | unsigned int refcount = size ? |
7af964d1 | 1283 | auto_zone_retain_count(zone, (void *)base) : 0; |
2bfd4448 A |
1284 | |
1285 | switch (type) { | |
1286 | case AUTO_OBJECT_SCANNED: | |
8972963c A |
1287 | case AUTO_OBJECT_UNSCANNED: |
1288 | case AUTO_OBJECT_ALL_POINTERS: { | |
b3962a83 | 1289 | const char *class_name = object_getClassName((id)base); |
7af964d1 A |
1290 | if ((0 == strcmp(class_name, "__NSCFType")) || (0 == strcmp(class_name, "NSCFType"))) { |
1291 | strlcat(buf, cf_class_for_object((void *)base), sizeof(buf)); | |
2bfd4448 | 1292 | } else { |
7af964d1 | 1293 | strlcat(buf, class_name, sizeof(buf)); |
2bfd4448 A |
1294 | } |
1295 | if (offset) { | |
7257e56c | 1296 | append_ivar_at_offset(buf, ((id)base)->ISA(), offset, sizeof(buf)); |
2bfd4448 A |
1297 | } |
1298 | APPEND_SIZE(size); | |
1299 | break; | |
1300 | } | |
1301 | case AUTO_MEMORY_SCANNED: | |
7af964d1 | 1302 | strlcat(buf, "{conservative-block}", sizeof(buf)); |
2bfd4448 A |
1303 | APPEND_SIZE(size); |
1304 | break; | |
1305 | case AUTO_MEMORY_UNSCANNED: | |
7af964d1 | 1306 | strlcat(buf, "{no-pointers-block}", sizeof(buf)); |
2bfd4448 A |
1307 | APPEND_SIZE(size); |
1308 | break; | |
8972963c A |
1309 | case AUTO_MEMORY_ALL_POINTERS: |
1310 | strlcat(buf, "{all-pointers-block}", sizeof(buf)); | |
1311 | APPEND_SIZE(size); | |
1312 | break; | |
1313 | case AUTO_MEMORY_ALL_WEAK_POINTERS: | |
1314 | strlcat(buf, "{all-weak-pointers-block}", sizeof(buf)); | |
1315 | APPEND_SIZE(size); | |
1316 | break; | |
1317 | case AUTO_TYPE_UNKNOWN: | |
1318 | strlcat(buf, "{uncollectable-memory}", sizeof(buf)); | |
1319 | break; | |
2bfd4448 | 1320 | default: |
8972963c | 1321 | strlcat(buf, "{unknown-memory-type}", sizeof(buf)); |
2bfd4448 A |
1322 | } |
1323 | ||
1324 | if (withRetainCount && refcount > 0) { | |
7af964d1 A |
1325 | strlcat(buf, " [[refcount=", sizeof(buf)); |
1326 | strlcati(buf, refcount, sizeof(buf)); | |
1327 | strlcat(buf, "]]", sizeof(buf)); | |
2bfd4448 A |
1328 | } |
1329 | ||
cd5f04f5 | 1330 | size_t len = 1 + strlen(buf); |
7257e56c | 1331 | result = (char *)malloc_zone_malloc(objc_debug_zone(), len); |
cd5f04f5 | 1332 | memcpy(result, buf, len); |
2bfd4448 A |
1333 | return result; |
1334 | ||
1335 | #undef APPEND_SIZE | |
1336 | } | |
1337 | ||
1338 | ||
2bfd4448 | 1339 | |
2bfd4448 | 1340 | |
2bfd4448 | 1341 | |
7af964d1 | 1342 | #endif |