2 * Copyright (c) 1999-2007 Apple Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
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
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.
21 * @APPLE_LICENSE_HEADER_END@
24 #include "objc-private.h"
25 #include "objc-sync.h"
28 // Allocate a lock only when needed. Since few locks are needed at any point
29 // in time, keep them on a single list.
33 typedef struct alignas(CacheLineSize) SyncData {
34 struct SyncData* nextData;
35 DisguisedPtr<objc_object> object;
36 int32_t threadCount; // number of THREADS using this block
37 recursive_mutex_t mutex;
42 unsigned int lockCount; // number of times THIS THREAD locked this block
45 typedef struct SyncCache {
46 unsigned int allocated;
48 SyncCacheItem list[0];
52 Fast cache: two fixed pthread keys store a single SyncCacheItem.
53 This avoids malloc of the SyncCache for threads that only synchronize
54 a single object at a time.
55 SYNC_DATA_DIRECT_KEY == SyncCacheItem.data
56 SYNC_COUNT_DIRECT_KEY == SyncCacheItem.lockCount
63 constexpr SyncList() : data(nil), lock(fork_unsafe_lock) { }
66 // Use multiple parallel lists to decrease contention among unrelated objects.
67 #define LOCK_FOR_OBJ(obj) sDataLists[obj].lock
68 #define LIST_FOR_OBJ(obj) sDataLists[obj].data
69 static StripedMap<SyncList> sDataLists;
72 enum usage { ACQUIRE, RELEASE, CHECK };
74 static SyncCache *fetch_cache(bool create)
76 _objc_pthread_data *data;
78 data = _objc_fetch_pthread_data(create);
79 if (!data) return NULL;
81 if (!data->syncCache) {
86 data->syncCache = (SyncCache *)
87 calloc(1, sizeof(SyncCache) + count*sizeof(SyncCacheItem));
88 data->syncCache->allocated = count;
92 // Make sure there's at least one open slot in the list.
93 if (data->syncCache->allocated == data->syncCache->used) {
94 data->syncCache->allocated *= 2;
95 data->syncCache = (SyncCache *)
96 realloc(data->syncCache, sizeof(SyncCache)
97 + data->syncCache->allocated * sizeof(SyncCacheItem));
100 return data->syncCache;
104 void _destroySyncCache(struct SyncCache *cache)
106 if (cache) free(cache);
110 static SyncData* id2data(id object, enum usage why)
112 spinlock_t *lockp = &LOCK_FOR_OBJ(object);
113 SyncData **listp = &LIST_FOR_OBJ(object);
114 SyncData* result = NULL;
116 #if SUPPORT_DIRECT_THREAD_KEYS
117 // Check per-thread single-entry fast cache for matching object
118 bool fastCacheOccupied = NO;
119 SyncData *data = (SyncData *)tls_get_direct(SYNC_DATA_DIRECT_KEY);
121 fastCacheOccupied = YES;
123 if (data->object == object) {
124 // Found a match in fast cache.
128 lockCount = (uintptr_t)tls_get_direct(SYNC_COUNT_DIRECT_KEY);
129 if (result->threadCount <= 0 || lockCount <= 0) {
130 _objc_fatal("id2data fastcache is buggy");
136 tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)lockCount);
141 tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)lockCount);
142 if (lockCount == 0) {
143 // remove from fast cache
144 tls_set_direct(SYNC_DATA_DIRECT_KEY, NULL);
145 // atomic because may collide with concurrent ACQUIRE
146 OSAtomicDecrement32Barrier(&result->threadCount);
159 // Check per-thread cache of already-owned locks for matching object
160 SyncCache *cache = fetch_cache(NO);
163 for (i = 0; i < cache->used; i++) {
164 SyncCacheItem *item = &cache->list[i];
165 if (item->data->object != object) continue;
169 if (result->threadCount <= 0 || item->lockCount <= 0) {
170 _objc_fatal("id2data cache is buggy");
179 if (item->lockCount == 0) {
180 // remove from per-thread cache
181 cache->list[i] = cache->list[--cache->used];
182 // atomic because may collide with concurrent ACQUIRE
183 OSAtomicDecrement32Barrier(&result->threadCount);
195 // Thread cache didn't find anything.
196 // Walk in-use list looking for matching object
197 // Spinlock prevents multiple threads from creating multiple
198 // locks for the same new object.
199 // We could keep the nodes in some hash table if we find that there are
200 // more than 20 or so distinct locks active, but we don't do that now.
206 SyncData* firstUnused = NULL;
207 for (p = *listp; p != NULL; p = p->nextData) {
208 if ( p->object == object ) {
210 // atomic because may collide with concurrent RELEASE
211 OSAtomicIncrement32Barrier(&result->threadCount);
214 if ( (firstUnused == NULL) && (p->threadCount == 0) )
218 // no SyncData currently associated with object
219 if ( (why == RELEASE) || (why == CHECK) )
222 // an unused one was found, use it
223 if ( firstUnused != NULL ) {
224 result = firstUnused;
225 result->object = (objc_object *)object;
226 result->threadCount = 1;
231 // Allocate a new SyncData and add to list.
232 // XXX allocating memory with a global lock held is bad practice,
233 // might be worth releasing the lock, allocating, and searching again.
234 // But since we never free these guys we won't be stuck in allocation very often.
235 posix_memalign((void **)&result, alignof(SyncData), sizeof(SyncData));
236 result->object = (objc_object *)object;
237 result->threadCount = 1;
238 new (&result->mutex) recursive_mutex_t(fork_unsafe_lock);
239 result->nextData = *listp;
245 // Only new ACQUIRE should get here.
246 // All RELEASE and CHECK and recursive ACQUIRE are
247 // handled by the per-thread caches above.
248 if (why == RELEASE) {
249 // Probably some thread is incorrectly exiting
250 // while the object is held by another thread.
253 if (why != ACQUIRE) _objc_fatal("id2data is buggy");
254 if (result->object != object) _objc_fatal("id2data is buggy");
256 #if SUPPORT_DIRECT_THREAD_KEYS
257 if (!fastCacheOccupied) {
258 // Save in fast thread cache
259 tls_set_direct(SYNC_DATA_DIRECT_KEY, result);
260 tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)1);
264 // Save in thread cache
265 if (!cache) cache = fetch_cache(YES);
266 cache->list[cache->used].data = result;
267 cache->list[cache->used].lockCount = 1;
277 void objc_sync_nil(void)
281 // Begin synchronizing on 'obj'.
282 // Allocates recursive mutex associated with 'obj' if needed.
283 // Returns OBJC_SYNC_SUCCESS once lock is acquired.
284 int objc_sync_enter(id obj)
286 int result = OBJC_SYNC_SUCCESS;
289 SyncData* data = id2data(obj, ACQUIRE);
293 // @synchronized(nil) does nothing
295 _objc_inform("NIL SYNC DEBUG: @synchronized(nil); set a breakpoint on objc_sync_nil to debug");
303 BOOL objc_sync_try_enter(id obj)
308 SyncData* data = id2data(obj, ACQUIRE);
310 result = data->mutex.tryLock();
312 // @synchronized(nil) does nothing
314 _objc_inform("NIL SYNC DEBUG: @synchronized(nil); set a breakpoint on objc_sync_nil to debug");
323 // End synchronizing on 'obj'.
324 // Returns OBJC_SYNC_SUCCESS or OBJC_SYNC_NOT_OWNING_THREAD_ERROR
325 int objc_sync_exit(id obj)
327 int result = OBJC_SYNC_SUCCESS;
330 SyncData* data = id2data(obj, RELEASE);
332 result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR;
334 bool okay = data->mutex.tryUnlock();
336 result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR;
340 // @synchronized(nil) does nothing