123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505 |
- /*
- * Copyright (c) 2010-2011 Apple Inc. All rights reserved.
- *
- * @APPLE_LICENSE_HEADER_START@
- *
- * This file contains Original Code and/or Modifications of Original Code
- * as defined in and that are subject to the Apple Public Source License
- * Version 2.0 (the 'License'). You may not use this file except in
- * compliance with the License. Please obtain a copy of the License at
- * http://www.opensource.apple.com/apsl/ and read it before using this
- * file.
- *
- * The Original Code and all software distributed under the License are
- * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
- * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
- * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
- * Please see the License for the specific language governing rights and
- * limitations under the License.
- *
- * @APPLE_LICENSE_HEADER_END@
- */
- #include "objc-private.h"
- #include "objc-weak.h"
- #include <stdint.h>
- #include <stdbool.h>
- #include <sys/types.h>
- #include <libkern/OSAtomic.h>
- #define TABLE_SIZE(entry) (entry->mask ? entry->mask + 1 : 0)
- static void append_referrer(weak_entry_t *entry, objc_object **new_referrer);
- BREAKPOINT_FUNCTION(
- void objc_weak_error(void)
- );
- static void bad_weak_table(weak_entry_t *entries)
- {
- _objc_fatal("bad weak table at %p. This may be a runtime bug or a "
- "memory error somewhere else.", entries);
- }
- /**
- * Unique hash function for object pointers only.
- *
- * @param key The object pointer
- *
- * @return Size unrestricted hash of pointer.
- */
- static inline uintptr_t hash_pointer(objc_object *key) {
- return ptr_hash((uintptr_t)key);
- }
- /**
- * Unique hash function for weak object pointers only.
- *
- * @param key The weak object pointer.
- *
- * @return Size unrestricted hash of pointer.
- */
- static inline uintptr_t w_hash_pointer(objc_object **key) {
- return ptr_hash((uintptr_t)key);
- }
- /**
- * Grow the entry's hash table of referrers. Rehashes each
- * of the referrers.
- *
- * @param entry Weak pointer hash set for a particular object.
- */
- __attribute__((noinline, used))
- static void grow_refs_and_insert(weak_entry_t *entry,
- objc_object **new_referrer)
- {
- assert(entry->out_of_line());
- size_t old_size = TABLE_SIZE(entry);
- size_t new_size = old_size ? old_size * 2 : 8;
- size_t num_refs = entry->num_refs;
- weak_referrer_t *old_refs = entry->referrers;
- entry->mask = new_size - 1;
-
- entry->referrers = (weak_referrer_t *)
- calloc(TABLE_SIZE(entry), sizeof(weak_referrer_t));
- entry->num_refs = 0;
- entry->max_hash_displacement = 0;
-
- for (size_t i = 0; i < old_size && num_refs > 0; i++) {
- if (old_refs[i] != nil) {
- append_referrer(entry, old_refs[i]);
- num_refs--;
- }
- }
- // Insert
- append_referrer(entry, new_referrer);
- if (old_refs) free(old_refs);
- }
- /**
- * Add the given referrer to set of weak pointers in this entry.
- * Does not perform duplicate checking (b/c weak pointers are never
- * added to a set twice).
- *
- * @param entry The entry holding the set of weak pointers.
- * @param new_referrer The new weak pointer to be added.
- */
- static void append_referrer(weak_entry_t *entry, objc_object **new_referrer)
- {
- if (! entry->out_of_line()) {
- // Try to insert inline.
- for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
- if (entry->inline_referrers[i] == nil) {
- entry->inline_referrers[i] = new_referrer;
- return;
- }
- }
- // Couldn't insert inline. Allocate out of line.
- weak_referrer_t *new_referrers = (weak_referrer_t *)
- calloc(WEAK_INLINE_COUNT, sizeof(weak_referrer_t));
- // This constructed table is invalid, but grow_refs_and_insert
- // will fix it and rehash it.
- for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
- new_referrers[i] = entry->inline_referrers[i];
- }
- entry->referrers = new_referrers;
- entry->num_refs = WEAK_INLINE_COUNT;
- entry->out_of_line_ness = REFERRERS_OUT_OF_LINE;
- entry->mask = WEAK_INLINE_COUNT-1;
- entry->max_hash_displacement = 0;
- }
- assert(entry->out_of_line());
- if (entry->num_refs >= TABLE_SIZE(entry) * 3/4) {
- return grow_refs_and_insert(entry, new_referrer);
- }
- size_t begin = w_hash_pointer(new_referrer) & (entry->mask);
- size_t index = begin;
- size_t hash_displacement = 0;
- while (entry->referrers[index] != nil) {
- hash_displacement++;
- index = (index+1) & entry->mask;
- if (index == begin) bad_weak_table(entry);
- }
- if (hash_displacement > entry->max_hash_displacement) {
- entry->max_hash_displacement = hash_displacement;
- }
- weak_referrer_t &ref = entry->referrers[index];
- ref = new_referrer;
- entry->num_refs++;
- }
- /**
- * Remove old_referrer from set of referrers, if it's present.
- * Does not remove duplicates, because duplicates should not exist.
- *
- * @todo this is slow if old_referrer is not present. Is this ever the case?
- *
- * @param entry The entry holding the referrers.
- * @param old_referrer The referrer to remove.
- */
- static void remove_referrer(weak_entry_t *entry, objc_object **old_referrer)
- {
- if (! entry->out_of_line()) {
- for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
- if (entry->inline_referrers[i] == old_referrer) {
- entry->inline_referrers[i] = nil;
- return;
- }
- }
- _objc_inform("Attempted to unregister unknown __weak variable "
- "at %p. This is probably incorrect use of "
- "objc_storeWeak() and objc_loadWeak(). "
- "Break on objc_weak_error to debug.\n",
- old_referrer);
- objc_weak_error();
- return;
- }
- size_t begin = w_hash_pointer(old_referrer) & (entry->mask);
- size_t index = begin;
- size_t hash_displacement = 0;
- while (entry->referrers[index] != old_referrer) {
- index = (index+1) & entry->mask;
- if (index == begin) bad_weak_table(entry);
- hash_displacement++;
- if (hash_displacement > entry->max_hash_displacement) {
- _objc_inform("Attempted to unregister unknown __weak variable "
- "at %p. This is probably incorrect use of "
- "objc_storeWeak() and objc_loadWeak(). "
- "Break on objc_weak_error to debug.\n",
- old_referrer);
- objc_weak_error();
- return;
- }
- }
- entry->referrers[index] = nil;
- entry->num_refs--;
- }
- /**
- * Add new_entry to the object's table of weak references.
- * Does not check whether the referent is already in the table.
- */
- static void weak_entry_insert(weak_table_t *weak_table, weak_entry_t *new_entry)
- {
- weak_entry_t *weak_entries = weak_table->weak_entries;
- assert(weak_entries != nil);
- size_t begin = hash_pointer(new_entry->referent) & (weak_table->mask);
- size_t index = begin;
- size_t hash_displacement = 0;
- while (weak_entries[index].referent != nil) {
- index = (index+1) & weak_table->mask;
- if (index == begin) bad_weak_table(weak_entries);
- hash_displacement++;
- }
- weak_entries[index] = *new_entry;
- weak_table->num_entries++;
- if (hash_displacement > weak_table->max_hash_displacement) {
- weak_table->max_hash_displacement = hash_displacement;
- }
- }
- static void weak_resize(weak_table_t *weak_table, size_t new_size)
- {
- size_t old_size = TABLE_SIZE(weak_table);
- weak_entry_t *old_entries = weak_table->weak_entries;
- weak_entry_t *new_entries = (weak_entry_t *)
- calloc(new_size, sizeof(weak_entry_t));
- weak_table->mask = new_size - 1;
- weak_table->weak_entries = new_entries;
- weak_table->max_hash_displacement = 0;
- weak_table->num_entries = 0; // restored by weak_entry_insert below
-
- if (old_entries) {
- weak_entry_t *entry;
- weak_entry_t *end = old_entries + old_size;
- for (entry = old_entries; entry < end; entry++) {
- if (entry->referent) {
- weak_entry_insert(weak_table, entry);
- }
- }
- free(old_entries);
- }
- }
- // Grow the given zone's table of weak references if it is full.
- static void weak_grow_maybe(weak_table_t *weak_table)
- {
- size_t old_size = TABLE_SIZE(weak_table);
- // Grow if at least 3/4 full.
- if (weak_table->num_entries >= old_size * 3 / 4) {
- weak_resize(weak_table, old_size ? old_size*2 : 64);
- }
- }
- // Shrink the table if it is mostly empty.
- static void weak_compact_maybe(weak_table_t *weak_table)
- {
- size_t old_size = TABLE_SIZE(weak_table);
- // Shrink if larger than 1024 buckets and at most 1/16 full.
- if (old_size >= 1024 && old_size / 16 >= weak_table->num_entries) {
- weak_resize(weak_table, old_size / 8);
- // leaves new table no more than 1/2 full
- }
- }
- /**
- * Remove entry from the zone's table of weak references.
- */
- static void weak_entry_remove(weak_table_t *weak_table, weak_entry_t *entry)
- {
- // remove entry
- if (entry->out_of_line()) free(entry->referrers);
- bzero(entry, sizeof(*entry));
- weak_table->num_entries--;
- weak_compact_maybe(weak_table);
- }
- /**
- * Return the weak reference table entry for the given referent.
- * If there is no entry for referent, return NULL.
- * Performs a lookup.
- *
- * @param weak_table
- * @param referent The object. Must not be nil.
- *
- * @return The table of weak referrers to this object.
- */
- static weak_entry_t *
- weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent)
- {
- assert(referent);
- weak_entry_t *weak_entries = weak_table->weak_entries;
- if (!weak_entries) return nil;
- size_t begin = hash_pointer(referent) & weak_table->mask;
- size_t index = begin;
- size_t hash_displacement = 0;
- while (weak_table->weak_entries[index].referent != referent) {
- index = (index+1) & weak_table->mask;
- if (index == begin) bad_weak_table(weak_table->weak_entries);
- hash_displacement++;
- if (hash_displacement > weak_table->max_hash_displacement) {
- return nil;
- }
- }
-
- return &weak_table->weak_entries[index];
- }
- /**
- * Unregister an already-registered weak reference.
- * This is used when referrer's storage is about to go away, but referent
- * isn't dead yet. (Otherwise, zeroing referrer later would be a
- * bad memory access.)
- * Does nothing if referent/referrer is not a currently active weak reference.
- * Does not zero referrer.
- *
- * FIXME currently requires old referent value to be passed in (lame)
- * FIXME unregistration should be automatic if referrer is collected
- *
- * @param weak_table The global weak table.
- * @param referent The object.
- * @param referrer The weak reference.
- */
- void
- weak_unregister_no_lock(weak_table_t *weak_table, id referent_id,
- id *referrer_id)
- {
- objc_object *referent = (objc_object *)referent_id;
- objc_object **referrer = (objc_object **)referrer_id;
- weak_entry_t *entry;
- if (!referent) return;
- if ((entry = weak_entry_for_referent(weak_table, referent))) {
- remove_referrer(entry, referrer);
- bool empty = true;
- if (entry->out_of_line() && entry->num_refs != 0) {
- empty = false;
- }
- else {
- for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
- if (entry->inline_referrers[i]) {
- empty = false;
- break;
- }
- }
- }
- if (empty) {
- weak_entry_remove(weak_table, entry);
- }
- }
- // Do not set *referrer = nil. objc_storeWeak() requires that the
- // value not change.
- }
- /**
- * Registers a new (object, weak pointer) pair. Creates a new weak
- * object entry if it does not exist.
- *
- * @param weak_table The global weak table.
- * @param referent The object pointed to by the weak reference.
- * @param referrer The weak pointer address.
- */
- id
- weak_register_no_lock(weak_table_t *weak_table, id referent_id,
- id *referrer_id, bool crashIfDeallocating)
- {
- objc_object *referent = (objc_object *)referent_id;
- objc_object **referrer = (objc_object **)referrer_id;
- if (!referent || referent->isTaggedPointer()) return referent_id;
- // ensure that the referenced object is viable
- bool deallocating;
- if (!referent->ISA()->hasCustomRR()) {
- deallocating = referent->rootIsDeallocating();
- }
- else {
- BOOL (*allowsWeakReference)(objc_object *, SEL) =
- (BOOL(*)(objc_object *, SEL))
- object_getMethodImplementation((id)referent,
- SEL_allowsWeakReference);
- if ((IMP)allowsWeakReference == _objc_msgForward) {
- return nil;
- }
- deallocating =
- ! (*allowsWeakReference)(referent, SEL_allowsWeakReference);
- }
- if (deallocating) {
- if (crashIfDeallocating) {
- _objc_fatal("Cannot form weak reference to instance (%p) of "
- "class %s. It is possible that this object was "
- "over-released, or is in the process of deallocation.",
- (void*)referent, object_getClassName((id)referent));
- } else {
- return nil;
- }
- }
- // now remember it and where it is being stored
- weak_entry_t *entry;
- if ((entry = weak_entry_for_referent(weak_table, referent))) {
- append_referrer(entry, referrer);
- }
- else {
- weak_entry_t new_entry(referent, referrer);
- weak_grow_maybe(weak_table);
- weak_entry_insert(weak_table, &new_entry);
- }
- // Do not set *referrer. objc_storeWeak() requires that the
- // value not change.
- return referent_id;
- }
- #if DEBUG
- bool
- weak_is_registered_no_lock(weak_table_t *weak_table, id referent_id)
- {
- return weak_entry_for_referent(weak_table, (objc_object *)referent_id);
- }
- #endif
- /**
- * Called by dealloc; nils out all weak pointers that point to the
- * provided object so that they can no longer be used.
- *
- * @param weak_table
- * @param referent The object being deallocated.
- */
- void
- weak_clear_no_lock(weak_table_t *weak_table, id referent_id)
- {
- objc_object *referent = (objc_object *)referent_id;
- weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
- if (entry == nil) {
- /// XXX shouldn't happen, but does with mismatched CF/objc
- //printf("XXX no entry for clear deallocating %p\n", referent);
- return;
- }
- // zero out references
- weak_referrer_t *referrers;
- size_t count;
-
- if (entry->out_of_line()) {
- referrers = entry->referrers;
- count = TABLE_SIZE(entry);
- }
- else {
- referrers = entry->inline_referrers;
- count = WEAK_INLINE_COUNT;
- }
-
- for (size_t i = 0; i < count; ++i) {
- objc_object **referrer = referrers[i];
- if (referrer) {
- if (*referrer == referent) {
- *referrer = nil;
- }
- else if (*referrer) {
- _objc_inform("__weak variable at %p holds %p instead of %p. "
- "This is probably incorrect use of "
- "objc_storeWeak() and objc_loadWeak(). "
- "Break on objc_weak_error to debug.\n",
- referrer, (void*)*referrer, (void*)referent);
- objc_weak_error();
- }
- }
- }
-
- weak_entry_remove(weak_table, entry);
- }
|