]> git.saurik.com Git - apple/objc4.git/blame - runtime/objc-weak.mm
objc4-818.2.tar.gz
[apple/objc4.git] / runtime / objc-weak.mm
CommitLineData
8972963c
A
1/*
2 * Copyright (c) 2010-2011 Apple Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
cd5f04f5 24#include "objc-private.h"
8972963c 25
7257e56c
A
26#include "objc-weak.h"
27
cd5f04f5
A
28#include <stdint.h>
29#include <stdbool.h>
30#include <sys/types.h>
31#include <libkern/OSAtomic.h>
8972963c 32
7257e56c 33#define TABLE_SIZE(entry) (entry->mask ? entry->mask + 1 : 0)
8972963c 34
8070259c
A
35static void append_referrer(weak_entry_t *entry, objc_object **new_referrer);
36
37BREAKPOINT_FUNCTION(
38 void objc_weak_error(void)
39);
8972963c 40
c1e772c4
A
41static void bad_weak_table(weak_entry_t *entries)
42{
43 _objc_fatal("bad weak table at %p. This may be a runtime bug or a "
44 "memory error somewhere else.", entries);
45}
46
7257e56c
A
47/**
48 * Unique hash function for object pointers only.
49 *
50 * @param key The object pointer
51 *
52 * @return Size unrestricted hash of pointer.
53 */
8070259c
A
54static inline uintptr_t hash_pointer(objc_object *key) {
55 return ptr_hash((uintptr_t)key);
7257e56c 56}
8972963c 57
7257e56c
A
58/**
59 * Unique hash function for weak object pointers only.
60 *
61 * @param key The weak object pointer.
62 *
63 * @return Size unrestricted hash of pointer.
64 */
8070259c
A
65static inline uintptr_t w_hash_pointer(objc_object **key) {
66 return ptr_hash((uintptr_t)key);
7257e56c 67}
8972963c 68
7257e56c
A
69/**
70 * Grow the entry's hash table of referrers. Rehashes each
71 * of the referrers.
72 *
73 * @param entry Weak pointer hash set for a particular object.
74 */
75__attribute__((noinline, used))
8070259c
A
76static void grow_refs_and_insert(weak_entry_t *entry,
77 objc_object **new_referrer)
7257e56c 78{
1807f628 79 ASSERT(entry->out_of_line());
8972963c 80
7257e56c
A
81 size_t old_size = TABLE_SIZE(entry);
82 size_t new_size = old_size ? old_size * 2 : 8;
8972963c 83
7257e56c
A
84 size_t num_refs = entry->num_refs;
85 weak_referrer_t *old_refs = entry->referrers;
86 entry->mask = new_size - 1;
87
88 entry->referrers = (weak_referrer_t *)
31875a97 89 calloc(TABLE_SIZE(entry), sizeof(weak_referrer_t));
7257e56c
A
90 entry->num_refs = 0;
91 entry->max_hash_displacement = 0;
92
93 for (size_t i = 0; i < old_size && num_refs > 0; i++) {
94 if (old_refs[i] != nil) {
95 append_referrer(entry, old_refs[i]);
96 num_refs--;
97 }
8972963c 98 }
7257e56c
A
99 // Insert
100 append_referrer(entry, new_referrer);
31875a97 101 if (old_refs) free(old_refs);
7257e56c 102}
8972963c 103
7257e56c
A
104/**
105 * Add the given referrer to set of weak pointers in this entry.
106 * Does not perform duplicate checking (b/c weak pointers are never
107 * added to a set twice).
108 *
109 * @param entry The entry holding the set of weak pointers.
110 * @param new_referrer The new weak pointer to be added.
111 */
8070259c 112static void append_referrer(weak_entry_t *entry, objc_object **new_referrer)
7257e56c 113{
c1e772c4 114 if (! entry->out_of_line()) {
7257e56c
A
115 // Try to insert inline.
116 for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
117 if (entry->inline_referrers[i] == nil) {
118 entry->inline_referrers[i] = new_referrer;
119 return;
120 }
121 }
8972963c 122
7257e56c
A
123 // Couldn't insert inline. Allocate out of line.
124 weak_referrer_t *new_referrers = (weak_referrer_t *)
31875a97 125 calloc(WEAK_INLINE_COUNT, sizeof(weak_referrer_t));
7257e56c
A
126 // This constructed table is invalid, but grow_refs_and_insert
127 // will fix it and rehash it.
128 for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
129 new_referrers[i] = entry->inline_referrers[i];
130 }
131 entry->referrers = new_referrers;
132 entry->num_refs = WEAK_INLINE_COUNT;
c1e772c4 133 entry->out_of_line_ness = REFERRERS_OUT_OF_LINE;
7257e56c
A
134 entry->mask = WEAK_INLINE_COUNT-1;
135 entry->max_hash_displacement = 0;
136 }
8972963c 137
1807f628 138 ASSERT(entry->out_of_line());
8972963c 139
7257e56c
A
140 if (entry->num_refs >= TABLE_SIZE(entry) * 3/4) {
141 return grow_refs_and_insert(entry, new_referrer);
8972963c 142 }
c1e772c4
A
143 size_t begin = w_hash_pointer(new_referrer) & (entry->mask);
144 size_t index = begin;
7257e56c 145 size_t hash_displacement = 0;
c1e772c4 146 while (entry->referrers[index] != nil) {
7257e56c 147 hash_displacement++;
c1e772c4
A
148 index = (index+1) & entry->mask;
149 if (index == begin) bad_weak_table(entry);
8972963c 150 }
7257e56c
A
151 if (hash_displacement > entry->max_hash_displacement) {
152 entry->max_hash_displacement = hash_displacement;
8972963c 153 }
7257e56c
A
154 weak_referrer_t &ref = entry->referrers[index];
155 ref = new_referrer;
156 entry->num_refs++;
157}
8972963c 158
7257e56c
A
159/**
160 * Remove old_referrer from set of referrers, if it's present.
161 * Does not remove duplicates, because duplicates should not exist.
162 *
8070259c 163 * @todo this is slow if old_referrer is not present. Is this ever the case?
7257e56c
A
164 *
165 * @param entry The entry holding the referrers.
166 * @param old_referrer The referrer to remove.
167 */
8070259c 168static void remove_referrer(weak_entry_t *entry, objc_object **old_referrer)
7257e56c 169{
c1e772c4 170 if (! entry->out_of_line()) {
7257e56c
A
171 for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
172 if (entry->inline_referrers[i] == old_referrer) {
173 entry->inline_referrers[i] = nil;
174 return;
175 }
176 }
8070259c
A
177 _objc_inform("Attempted to unregister unknown __weak variable "
178 "at %p. This is probably incorrect use of "
179 "objc_storeWeak() and objc_loadWeak(). "
180 "Break on objc_weak_error to debug.\n",
7257e56c 181 old_referrer);
8070259c
A
182 objc_weak_error();
183 return;
7257e56c 184 }
8972963c 185
c1e772c4
A
186 size_t begin = w_hash_pointer(old_referrer) & (entry->mask);
187 size_t index = begin;
7257e56c
A
188 size_t hash_displacement = 0;
189 while (entry->referrers[index] != old_referrer) {
190 index = (index+1) & entry->mask;
c1e772c4 191 if (index == begin) bad_weak_table(entry);
7257e56c
A
192 hash_displacement++;
193 if (hash_displacement > entry->max_hash_displacement) {
8070259c
A
194 _objc_inform("Attempted to unregister unknown __weak variable "
195 "at %p. This is probably incorrect use of "
196 "objc_storeWeak() and objc_loadWeak(). "
197 "Break on objc_weak_error to debug.\n",
7257e56c 198 old_referrer);
8070259c 199 objc_weak_error();
7257e56c
A
200 return;
201 }
202 }
203 entry->referrers[index] = nil;
204 entry->num_refs--;
205}
8972963c 206
7257e56c
A
207/**
208 * Add new_entry to the object's table of weak references.
209 * Does not check whether the referent is already in the table.
210 */
211static void weak_entry_insert(weak_table_t *weak_table, weak_entry_t *new_entry)
212{
213 weak_entry_t *weak_entries = weak_table->weak_entries;
1807f628 214 ASSERT(weak_entries != nil);
8972963c 215
c1e772c4
A
216 size_t begin = hash_pointer(new_entry->referent) & (weak_table->mask);
217 size_t index = begin;
7257e56c
A
218 size_t hash_displacement = 0;
219 while (weak_entries[index].referent != nil) {
220 index = (index+1) & weak_table->mask;
c1e772c4 221 if (index == begin) bad_weak_table(weak_entries);
7257e56c
A
222 hash_displacement++;
223 }
8972963c 224
7257e56c
A
225 weak_entries[index] = *new_entry;
226 weak_table->num_entries++;
8972963c 227
7257e56c
A
228 if (hash_displacement > weak_table->max_hash_displacement) {
229 weak_table->max_hash_displacement = hash_displacement;
230 }
8972963c
A
231}
232
8972963c 233
7257e56c 234static void weak_resize(weak_table_t *weak_table, size_t new_size)
8972963c 235{
7257e56c
A
236 size_t old_size = TABLE_SIZE(weak_table);
237
238 weak_entry_t *old_entries = weak_table->weak_entries;
239 weak_entry_t *new_entries = (weak_entry_t *)
31875a97 240 calloc(new_size, sizeof(weak_entry_t));
7257e56c
A
241
242 weak_table->mask = new_size - 1;
243 weak_table->weak_entries = new_entries;
244 weak_table->max_hash_displacement = 0;
245 weak_table->num_entries = 0; // restored by weak_entry_insert below
8972963c 246
7257e56c
A
247 if (old_entries) {
248 weak_entry_t *entry;
249 weak_entry_t *end = old_entries + old_size;
250 for (entry = old_entries; entry < end; entry++) {
251 if (entry->referent) {
252 weak_entry_insert(weak_table, entry);
253 }
8972963c 254 }
31875a97 255 free(old_entries);
8972963c 256 }
8972963c
A
257}
258
7257e56c
A
259// Grow the given zone's table of weak references if it is full.
260static void weak_grow_maybe(weak_table_t *weak_table)
8972963c 261{
7257e56c
A
262 size_t old_size = TABLE_SIZE(weak_table);
263
264 // Grow if at least 3/4 full.
265 if (weak_table->num_entries >= old_size * 3 / 4) {
266 weak_resize(weak_table, old_size ? old_size*2 : 64);
8972963c 267 }
8972963c
A
268}
269
7257e56c
A
270// Shrink the table if it is mostly empty.
271static void weak_compact_maybe(weak_table_t *weak_table)
8972963c 272{
7257e56c
A
273 size_t old_size = TABLE_SIZE(weak_table);
274
275 // Shrink if larger than 1024 buckets and at most 1/16 full.
276 if (old_size >= 1024 && old_size / 16 >= weak_table->num_entries) {
277 weak_resize(weak_table, old_size / 8);
278 // leaves new table no more than 1/2 full
8972963c 279 }
8972963c
A
280}
281
282
7257e56c
A
283/**
284 * Remove entry from the zone's table of weak references.
285 */
286static void weak_entry_remove(weak_table_t *weak_table, weak_entry_t *entry)
8972963c 287{
7257e56c 288 // remove entry
c1e772c4 289 if (entry->out_of_line()) free(entry->referrers);
7257e56c 290 bzero(entry, sizeof(*entry));
8972963c 291
7257e56c 292 weak_table->num_entries--;
8972963c 293
7257e56c 294 weak_compact_maybe(weak_table);
8972963c
A
295}
296
297
7257e56c
A
298/**
299 * Return the weak reference table entry for the given referent.
300 * If there is no entry for referent, return NULL.
301 * Performs a lookup.
302 *
303 * @param weak_table
304 * @param referent The object. Must not be nil.
305 *
306 * @return The table of weak referrers to this object.
307 */
8070259c
A
308static weak_entry_t *
309weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent)
8972963c 310{
1807f628 311 ASSERT(referent);
8972963c 312
8972963c 313 weak_entry_t *weak_entries = weak_table->weak_entries;
8972963c 314
7257e56c 315 if (!weak_entries) return nil;
8972963c 316
c1e772c4
A
317 size_t begin = hash_pointer(referent) & weak_table->mask;
318 size_t index = begin;
7257e56c
A
319 size_t hash_displacement = 0;
320 while (weak_table->weak_entries[index].referent != referent) {
321 index = (index+1) & weak_table->mask;
c1e772c4 322 if (index == begin) bad_weak_table(weak_table->weak_entries);
7257e56c
A
323 hash_displacement++;
324 if (hash_displacement > weak_table->max_hash_displacement) {
325 return nil;
8972963c
A
326 }
327 }
8972963c 328
7257e56c 329 return &weak_table->weak_entries[index];
8972963c
A
330}
331
7257e56c
A
332/**
333 * Unregister an already-registered weak reference.
334 * This is used when referrer's storage is about to go away, but referent
335 * isn't dead yet. (Otherwise, zeroing referrer later would be a
336 * bad memory access.)
337 * Does nothing if referent/referrer is not a currently active weak reference.
338 * Does not zero referrer.
339 *
340 * FIXME currently requires old referent value to be passed in (lame)
341 * FIXME unregistration should be automatic if referrer is collected
342 *
343 * @param weak_table The global weak table.
344 * @param referent The object.
345 * @param referrer The weak reference.
346 */
8070259c
A
347void
348weak_unregister_no_lock(weak_table_t *weak_table, id referent_id,
349 id *referrer_id)
8972963c 350{
8070259c
A
351 objc_object *referent = (objc_object *)referent_id;
352 objc_object **referrer = (objc_object **)referrer_id;
353
8972963c
A
354 weak_entry_t *entry;
355
7257e56c 356 if (!referent) return;
8972963c 357
7257e56c
A
358 if ((entry = weak_entry_for_referent(weak_table, referent))) {
359 remove_referrer(entry, referrer);
360 bool empty = true;
c1e772c4 361 if (entry->out_of_line() && entry->num_refs != 0) {
7257e56c 362 empty = false;
8972963c 363 }
7257e56c
A
364 else {
365 for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
366 if (entry->inline_referrers[i]) {
367 empty = false;
368 break;
8972963c
A
369 }
370 }
371 }
7257e56c
A
372
373 if (empty) {
374 weak_entry_remove(weak_table, entry);
375 }
8972963c 376 }
8972963c 377
7257e56c
A
378 // Do not set *referrer = nil. objc_storeWeak() requires that the
379 // value not change.
380}
8972963c 381
7257e56c
A
382/**
383 * Registers a new (object, weak pointer) pair. Creates a new weak
384 * object entry if it does not exist.
385 *
386 * @param weak_table The global weak table.
387 * @param referent The object pointed to by the weak reference.
388 * @param referrer The weak pointer address.
389 */
390id
31875a97 391weak_register_no_lock(weak_table_t *weak_table, id referent_id,
34d5b5e8 392 id *referrer_id, WeakRegisterDeallocatingOptions deallocatingOptions)
7257e56c 393{
8070259c
A
394 objc_object *referent = (objc_object *)referent_id;
395 objc_object **referrer = (objc_object **)referrer_id;
396
34d5b5e8 397 if (referent->isTaggedPointerOrNil()) return referent_id;
8070259c
A
398
399 // ensure that the referenced object is viable
34d5b5e8
A
400 if (deallocatingOptions == ReturnNilIfDeallocating ||
401 deallocatingOptions == CrashIfDeallocating) {
402 bool deallocating;
403 if (!referent->ISA()->hasCustomRR()) {
404 deallocating = referent->rootIsDeallocating();
8972963c 405 }
34d5b5e8
A
406 else {
407 // Use lookUpImpOrForward so we can avoid the assert in
408 // class_getInstanceMethod, since we intentionally make this
409 // callout with the lock held.
410 auto allowsWeakReference = (BOOL(*)(objc_object *, SEL))
411 lookUpImpOrForwardTryCache((id)referent, @selector(allowsWeakReference),
412 referent->getIsa());
413 if ((IMP)allowsWeakReference == _objc_msgForward) {
414 return nil;
415 }
416 deallocating =
1807f628 417 ! (*allowsWeakReference)(referent, @selector(allowsWeakReference));
34d5b5e8 418 }
7257e56c 419
34d5b5e8
A
420 if (deallocating) {
421 if (deallocatingOptions == CrashIfDeallocating) {
422 _objc_fatal("Cannot form weak reference to instance (%p) of "
423 "class %s. It is possible that this object was "
424 "over-released, or is in the process of deallocation.",
425 (void*)referent, object_getClassName((id)referent));
426 } else {
427 return nil;
428 }
31875a97 429 }
8070259c
A
430 }
431
432 // now remember it and where it is being stored
433 weak_entry_t *entry;
434 if ((entry = weak_entry_for_referent(weak_table, referent))) {
435 append_referrer(entry, referrer);
436 }
437 else {
c1e772c4 438 weak_entry_t new_entry(referent, referrer);
8070259c
A
439 weak_grow_maybe(weak_table);
440 weak_entry_insert(weak_table, &new_entry);
8972963c
A
441 }
442
443 // Do not set *referrer. objc_storeWeak() requires that the
444 // value not change.
445
8070259c
A
446 return referent_id;
447}
448
449
31875a97 450#if DEBUG
8070259c
A
451bool
452weak_is_registered_no_lock(weak_table_t *weak_table, id referent_id)
453{
454 return weak_entry_for_referent(weak_table, (objc_object *)referent_id);
8972963c 455}
8070259c
A
456#endif
457
8972963c 458
7257e56c
A
459/**
460 * Called by dealloc; nils out all weak pointers that point to the
461 * provided object so that they can no longer be used.
462 *
463 * @param weak_table
464 * @param referent The object being deallocated.
465 */
466void
8070259c 467weak_clear_no_lock(weak_table_t *weak_table, id referent_id)
7257e56c 468{
8070259c
A
469 objc_object *referent = (objc_object *)referent_id;
470
7257e56c
A
471 weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
472 if (entry == nil) {
473 /// XXX shouldn't happen, but does with mismatched CF/objc
474 //printf("XXX no entry for clear deallocating %p\n", referent);
475 return;
476 }
8972963c 477
7257e56c
A
478 // zero out references
479 weak_referrer_t *referrers;
480 size_t count;
481
c1e772c4 482 if (entry->out_of_line()) {
7257e56c
A
483 referrers = entry->referrers;
484 count = TABLE_SIZE(entry);
485 }
486 else {
487 referrers = entry->inline_referrers;
488 count = WEAK_INLINE_COUNT;
489 }
490
491 for (size_t i = 0; i < count; ++i) {
8070259c 492 objc_object **referrer = referrers[i];
7257e56c
A
493 if (referrer) {
494 if (*referrer == referent) {
495 *referrer = nil;
496 }
497 else if (*referrer) {
8070259c
A
498 _objc_inform("__weak variable at %p holds %p instead of %p. "
499 "This is probably incorrect use of "
500 "objc_storeWeak() and objc_loadWeak(). "
501 "Break on objc_weak_error to debug.\n",
502 referrer, (void*)*referrer, (void*)referent);
503 objc_weak_error();
8972963c 504 }
8972963c
A
505 }
506 }
7257e56c
A
507
508 weak_entry_remove(weak_table, entry);
509}
510