1 1.1 mrg /* GNU Objective C Runtime @synchronized implementation 2 1.10 mrg Copyright (C) 2010-2022 Free Software Foundation, Inc. 3 1.1 mrg Contributed by Nicola Pero <nicola.pero (at) meta-innovation.com> 4 1.1 mrg 5 1.1 mrg This file is part of GCC. 6 1.1 mrg 7 1.1 mrg GCC is free software; you can redistribute it and/or modify it under the 8 1.1 mrg terms of the GNU General Public License as published by the Free Software 9 1.1 mrg Foundation; either version 3, or (at your option) any later version. 10 1.1 mrg 11 1.1 mrg GCC is distributed in the hope that it will be useful, but WITHOUT ANY 12 1.1 mrg WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 1.1 mrg FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 14 1.1 mrg details. 15 1.1 mrg 16 1.1 mrg Under Section 7 of GPL version 3, you are granted additional 17 1.1 mrg permissions described in the GCC Runtime Library Exception, version 18 1.1 mrg 3.1, as published by the Free Software Foundation. 19 1.1 mrg 20 1.1 mrg You should have received a copy of the GNU General Public License and 21 1.1 mrg a copy of the GCC Runtime Library Exception along with this program; 22 1.1 mrg see the files COPYING3 and COPYING.RUNTIME respectively. If not, see 23 1.1 mrg <http://www.gnu.org/licenses/>. */ 24 1.1 mrg 25 1.1 mrg /* This file implements objc_sync_enter() and objc_sync_exit(), the 26 1.1 mrg two functions required to support @synchronized(). 27 1.1 mrg 28 1.1 mrg objc_sync_enter(object) needs to get a recursive lock associated 29 1.1 mrg with 'object', and lock it. 30 1.1 mrg 31 1.1 mrg objc_sync_exit(object) needs to get the recursive lock associated 32 1.1 mrg with 'object', and unlock it. */ 33 1.1 mrg 34 1.1 mrg /* To avoid the overhead of continuously allocating and deallocating 35 1.1 mrg locks, we implement a pool of locks. When a lock is needed for an 36 1.1 mrg object, we get a lock from the pool and associate it with the 37 1.1 mrg object. 38 1.1 mrg 39 1.1 mrg The lock pool need to be protected by its own lock (the 40 1.1 mrg "protection" lock), which has to be locked then unlocked each time 41 1.1 mrg objc_sync_enter() and objc_sync_exit() are called. To reduce the 42 1.1 mrg contention on the protection lock, instead of a single pool with a 43 1.1 mrg single (global) protection lock we use a number of smaller pools, 44 1.1 mrg each with its own pool protection lock. To decide which lock pool 45 1.1 mrg to use for each object, we compute a hash from the object pointer. 46 1.1 mrg 47 1.1 mrg The implementation of each lock pool uses a linked list of all the 48 1.1 mrg locks in the pool (both unlocked, and locked); this works in the 49 1.1 mrg assumption that the number of locks concurrently required is very 50 1.1 mrg low. In practice, it seems that you rarely see more than a few 51 1.1 mrg locks ever concurrently required. 52 1.1 mrg 53 1.1 mrg A standard case is a thread acquiring a lock recursively, over and 54 1.1 mrg over again: for example when most methods of a class are protected 55 1.1 mrg by @synchronized(self) but they also call each other. We use 56 1.1 mrg thread-local storage to implement a cache and optimize this case. 57 1.1 mrg The cache stores locks that the thread successfully acquired, 58 1.1 mrg allowing objc_sync_enter() and objc_sync_exit() to locate a lock 59 1.1 mrg which is already held by the current thread without having to use 60 1.1 mrg any protection lock or synchronization mechanism. It can so detect 61 1.1 mrg recursive locks/unlocks, and transform them into no-ops that 62 1.1 mrg require no actual locking or synchronization mechanisms at all. */ 63 1.1 mrg 64 1.1 mrg /* You can disable the thread-local cache (most likely to benchmark 65 1.1 mrg the code with and without it) by compiling with 66 1.1 mrg -DSYNC_CACHE_DISABLE, or commenting out the following line. */ 67 1.1 mrg /* #define SYNC_CACHE_DISABLE */ 68 1.1 mrg 69 1.1 mrg /* If thread-local storage is not available, automatically disable the 70 1.1 mrg cache. */ 71 1.1 mrg #ifndef HAVE_TLS 72 1.1 mrg # define SYNC_CACHE_DISABLE 73 1.1 mrg #endif 74 1.1 mrg 75 1.1 mrg #include "objc-private/common.h" 76 1.1 mrg #include "objc/objc-sync.h" /* For objc_sync_enter(), objc_sync_exit() */ 77 1.1 mrg #include "objc/runtime.h" /* For objc_malloc() */ 78 1.1 mrg #include "objc/thr.h" /* For objc_mutex_loc() and similar */ 79 1.1 mrg #include "objc-private/objc-sync.h" /* For __objc_sync_init() */ 80 1.1 mrg 81 1.1 mrg /* We have 32 pools of locks, each of them protected by its own 82 1.1 mrg protection lock. It's tempting to increase this number to reduce 83 1.1 mrg contention; but in our tests it is high enough. */ 84 1.1 mrg #define SYNC_NUMBER_OF_POOLS 32 85 1.1 mrg 86 1.1 mrg /* Given an object, it determines which pool contains the associated 87 1.1 mrg lock. */ 88 1.1 mrg #define SYNC_OBJECT_HASH(OBJECT) ((((size_t)OBJECT >> 8) ^ (size_t)OBJECT) & (SYNC_NUMBER_OF_POOLS - 1)) 89 1.1 mrg 90 1.1 mrg /* The locks protecting each pool. */ 91 1.1 mrg static objc_mutex_t sync_pool_protection_locks[SYNC_NUMBER_OF_POOLS]; 92 1.1 mrg 93 1.1 mrg /* The data structure (linked list) holding the locks. */ 94 1.1 mrg typedef struct lock_node 95 1.1 mrg { 96 1.1 mrg /* Pointer to next entry on the list. NULL indicates end of list. 97 1.1 mrg You need to hold the appropriate sync_pool_protection_locks[N] to 98 1.1 mrg read or write this variable. */ 99 1.1 mrg struct lock_node *next; 100 1.1 mrg 101 1.1 mrg /* The (recursive) lock. Allocated when the node is created, and 102 1.1 mrg always not-NULL, and unchangeable, after that. */ 103 1.1 mrg objc_mutex_t lock; 104 1.1 mrg 105 1.1 mrg /* This is how many times the objc_mutex_lock() has been called on 106 1.1 mrg the lock (it is 0 when the lock is unused). Used to track when 107 1.1 mrg the lock is no longer associated with an object and can be reused 108 1.1 mrg for another object. It records "real" locks, potentially (but 109 1.1 mrg not necessarily) by multiple threads. You need to hold the 110 1.1 mrg appropriate sync_pool_protection_locks[N] to read or write this 111 1.1 mrg variable. */ 112 1.1 mrg unsigned int usage_count; 113 1.1 mrg 114 1.1 mrg /* The object that the lock is associated with. This variable can 115 1.1 mrg only be written when holding the sync_pool_protection_locks[N] 116 1.1 mrg and when node->usage_count == 0, ie, the lock is not being used. 117 1.1 mrg You can read this variable either when you hold the 118 1.1 mrg sync_pool_protection_locks[N] or when you hold node->lock, 119 1.1 mrg because in that case you know that node->usage_count can't get to 120 1.1 mrg zero until you release the lock. It is valid to have usage_count 121 1.1 mrg == 0 and object != nil; in that case, the lock is not currently 122 1.1 mrg being used, but is still currently associated with the 123 1.1 mrg object. */ 124 1.1 mrg id object; 125 1.1 mrg 126 1.1 mrg /* This is a counter reserved for use by the thread currently 127 1.1 mrg holding the lock. So, you need to hold node->lock to read or 128 1.1 mrg write this variable. It is normally 0, and if the cache is not 129 1.1 mrg being used, it is kept at 0 (even if recursive locks are being 130 1.1 mrg done; in that case, no difference is made between recursive and 131 1.1 mrg non-recursive locks: they all increase usage_count, and call 132 1.1 mrg objc_mutex_lock()). When the cache is being used, a thread may 133 1.1 mrg be able to find a lock that it already holds using the cache; in 134 1.1 mrg that case, to perform additional locks/unlocks it can 135 1.1 mrg increase/decrease the recursive_usage_count (which does not 136 1.1 mrg require any synchronization with other threads, since it's 137 1.1 mrg protected by the node->lock itself) instead of the usage_count 138 1.1 mrg (which requires locking the pool protection lock). And it can 139 1.1 mrg skip the call to objc_mutex_lock/unlock too. */ 140 1.1 mrg unsigned int recursive_usage_count; 141 1.1 mrg } *lock_node_ptr; 142 1.1 mrg 143 1.1 mrg 144 1.1 mrg /* The pools of locks. Each of them is a linked list of lock_nodes. 145 1.1 mrg In the list we keep both unlocked and locked nodes. */ 146 1.1 mrg static lock_node_ptr sync_pool_array[SYNC_NUMBER_OF_POOLS]; 147 1.1 mrg 148 1.1 mrg #ifndef SYNC_CACHE_DISABLE 149 1.1 mrg /* We store a cache of locks acquired by each thread in thread-local 150 1.1 mrg storage. */ 151 1.1 mrg static __thread lock_node_ptr *lock_cache = NULL; 152 1.1 mrg 153 1.1 mrg /* This is a conservative implementation that uses a static array of 154 1.1 mrg fixed size as cache. Because the cache is an array that we scan 155 1.1 mrg linearly, the bigger it is, the slower it gets. This does not 156 1.1 mrg matter much at small sizes (eg, the overhead of checking 8 cache 157 1.1 mrg slots instead of 4 is very small compared to the other overheads 158 1.1 mrg involved such as function calls and lock/unlock operations), but at 159 1.1 mrg large sizes it becomes important as obviously there is a size over 160 1.1 mrg which using the cache backfires: the lookup is so slow that the 161 1.1 mrg cache slows down the software instead of speeding it up. In 162 1.1 mrg practice, it seems that most threads use a small number of 163 1.1 mrg concurrent locks, so we have a conservative implementation with a 164 1.1 mrg fixed-size cache of 8 locks which gives a very predictable 165 1.1 mrg behaviour. If a thread locks lots of different locks, only the 166 1.1 mrg first 8 get the speed benefits of the cache, but the cache remains 167 1.1 mrg always small, fast and predictable. 168 1.1 mrg 169 1.1 mrg SYNC_CACHE_SIZE is the size of the lock cache for each thread. */ 170 1.1 mrg #define SYNC_CACHE_SIZE 8 171 1.1 mrg #endif /* SYNC_CACHE_DISABLE */ 172 1.1 mrg 173 1.1 mrg /* Called at startup by init.c. */ 174 1.1 mrg void 175 1.1 mrg __objc_sync_init (void) 176 1.1 mrg { 177 1.1 mrg int i; 178 1.1 mrg 179 1.1 mrg for (i = 0; i < SYNC_NUMBER_OF_POOLS; i++) 180 1.1 mrg { 181 1.1 mrg lock_node_ptr new_node; 182 1.1 mrg 183 1.1 mrg /* Create a protection lock for each pool. */ 184 1.1 mrg sync_pool_protection_locks[i] = objc_mutex_allocate (); 185 1.1 mrg 186 1.1 mrg /* Preallocate a lock per pool. */ 187 1.1 mrg new_node = objc_malloc (sizeof (struct lock_node)); 188 1.1 mrg new_node->lock = objc_mutex_allocate (); 189 1.1 mrg new_node->object = nil; 190 1.1 mrg new_node->usage_count = 0; 191 1.1 mrg new_node->recursive_usage_count = 0; 192 1.1 mrg new_node->next = NULL; 193 1.1 mrg 194 1.1 mrg sync_pool_array[i] = new_node; 195 1.1 mrg } 196 1.1 mrg } 197 1.1 mrg 198 1.1 mrg int 199 1.1 mrg objc_sync_enter (id object) 200 1.1 mrg { 201 1.1 mrg #ifndef SYNC_CACHE_DISABLE 202 1.1 mrg int free_cache_slot; 203 1.1 mrg #endif 204 1.1 mrg int hash; 205 1.1 mrg lock_node_ptr node; 206 1.1 mrg lock_node_ptr unused_node; 207 1.1 mrg 208 1.1 mrg if (object == nil) 209 1.1 mrg return OBJC_SYNC_SUCCESS; 210 1.1 mrg 211 1.1 mrg #ifndef SYNC_CACHE_DISABLE 212 1.1 mrg if (lock_cache == NULL) 213 1.1 mrg { 214 1.1 mrg /* Note that this calloc only happen only once per thread, the 215 1.1 mrg very first time a thread does a objc_sync_enter(). */ 216 1.1 mrg lock_cache = objc_calloc (SYNC_CACHE_SIZE, sizeof (lock_node_ptr)); 217 1.1 mrg } 218 1.1 mrg 219 1.1 mrg /* Check the cache to see if we have a record of having already 220 1.1 mrg locked the lock corresponding to this object. While doing so, 221 1.1 mrg keep track of the first free cache node in case we need it 222 1.1 mrg later. */ 223 1.1 mrg node = NULL; 224 1.1 mrg free_cache_slot = -1; 225 1.1 mrg 226 1.1 mrg { 227 1.1 mrg int i; 228 1.1 mrg for (i = 0; i < SYNC_CACHE_SIZE; i++) 229 1.1 mrg { 230 1.1 mrg lock_node_ptr locked_node = lock_cache[i]; 231 1.1 mrg 232 1.1 mrg if (locked_node == NULL) 233 1.1 mrg { 234 1.1 mrg if (free_cache_slot == -1) 235 1.1 mrg free_cache_slot = i; 236 1.1 mrg } 237 1.1 mrg else if (locked_node->object == object) 238 1.1 mrg { 239 1.1 mrg node = locked_node; 240 1.1 mrg break; 241 1.1 mrg } 242 1.1 mrg } 243 1.1 mrg } 244 1.1 mrg 245 1.1 mrg if (node != NULL) 246 1.1 mrg { 247 1.1 mrg /* We found the lock. Increase recursive_usage_count, which is 248 1.1 mrg protected by node->lock, which we already hold. */ 249 1.1 mrg node->recursive_usage_count++; 250 1.1 mrg 251 1.1 mrg /* There is no need to actually lock anything, since we already 252 1.1 mrg hold the lock. Correspondingly, objc_sync_exit() will just 253 1.1 mrg decrease recursive_usage_count and do nothing to unlock. */ 254 1.1 mrg return OBJC_SYNC_SUCCESS; 255 1.1 mrg } 256 1.1 mrg #endif /* SYNC_CACHE_DISABLE */ 257 1.1 mrg 258 1.1 mrg /* The following is the standard lookup for the lock in the standard 259 1.1 mrg pool lock. It requires a pool protection lock. */ 260 1.1 mrg hash = SYNC_OBJECT_HASH(object); 261 1.1 mrg 262 1.1 mrg /* Search for an existing lock for 'object'. While searching, make 263 1.1 mrg note of any unused lock if we find any. */ 264 1.1 mrg unused_node = NULL; 265 1.1 mrg 266 1.1 mrg objc_mutex_lock (sync_pool_protection_locks[hash]); 267 1.1 mrg 268 1.1 mrg node = sync_pool_array[hash]; 269 1.1 mrg 270 1.1 mrg while (node != NULL) 271 1.1 mrg { 272 1.1 mrg if (node->object == object) 273 1.1 mrg { 274 1.1 mrg /* We found the lock. */ 275 1.1 mrg node->usage_count++; 276 1.1 mrg objc_mutex_unlock (sync_pool_protection_locks[hash]); 277 1.1 mrg 278 1.1 mrg #ifndef SYNC_CACHE_DISABLE 279 1.1 mrg /* Put it in the cache. */ 280 1.1 mrg if (free_cache_slot != -1) 281 1.1 mrg lock_cache[free_cache_slot] = node; 282 1.1 mrg #endif 283 1.1 mrg 284 1.1 mrg /* Lock it. */ 285 1.1 mrg objc_mutex_lock (node->lock); 286 1.1 mrg 287 1.1 mrg return OBJC_SYNC_SUCCESS; 288 1.1 mrg } 289 1.1 mrg 290 1.1 mrg if (unused_node == NULL && node->usage_count == 0) 291 1.1 mrg { 292 1.1 mrg /* We found the first unused node. Record it. */ 293 1.1 mrg unused_node = node; 294 1.1 mrg } 295 1.1 mrg 296 1.1 mrg node = node->next; 297 1.1 mrg } 298 1.1 mrg 299 1.1 mrg /* An existing lock for 'object' could not be found. */ 300 1.1 mrg if (unused_node != NULL) 301 1.1 mrg { 302 1.1 mrg /* But we found a unused lock; use it. */ 303 1.1 mrg unused_node->object = object; 304 1.1 mrg unused_node->usage_count = 1; 305 1.1 mrg unused_node->recursive_usage_count = 0; 306 1.1 mrg objc_mutex_unlock (sync_pool_protection_locks[hash]); 307 1.1 mrg 308 1.1 mrg #ifndef SYNC_CACHE_DISABLE 309 1.1 mrg if (free_cache_slot != -1) 310 1.1 mrg lock_cache[free_cache_slot] = unused_node; 311 1.1 mrg #endif 312 1.1 mrg 313 1.1 mrg objc_mutex_lock (unused_node->lock); 314 1.1 mrg 315 1.1 mrg return OBJC_SYNC_SUCCESS; 316 1.1 mrg } 317 1.1 mrg else 318 1.1 mrg { 319 1.1 mrg /* There are no unused nodes; allocate a new node. */ 320 1.1 mrg lock_node_ptr new_node; 321 1.1 mrg 322 1.1 mrg /* Create the node. */ 323 1.1 mrg new_node = objc_malloc (sizeof (struct lock_node)); 324 1.1 mrg new_node->lock = objc_mutex_allocate (); 325 1.1 mrg new_node->object = object; 326 1.1 mrg new_node->usage_count = 1; 327 1.1 mrg new_node->recursive_usage_count = 0; 328 1.1 mrg 329 1.1 mrg /* Attach it at the beginning of the pool. */ 330 1.1 mrg new_node->next = sync_pool_array[hash]; 331 1.1 mrg sync_pool_array[hash] = new_node; 332 1.1 mrg objc_mutex_unlock (sync_pool_protection_locks[hash]); 333 1.1 mrg 334 1.1 mrg #ifndef SYNC_CACHE_DISABLE 335 1.1 mrg if (free_cache_slot != -1) 336 1.1 mrg lock_cache[free_cache_slot] = new_node; 337 1.1 mrg #endif 338 1.1 mrg 339 1.1 mrg objc_mutex_lock (new_node->lock); 340 1.1 mrg 341 1.1 mrg return OBJC_SYNC_SUCCESS; 342 1.1 mrg } 343 1.1 mrg } 344 1.1 mrg 345 1.1 mrg int 346 1.1 mrg objc_sync_exit (id object) 347 1.1 mrg { 348 1.1 mrg int hash; 349 1.1 mrg lock_node_ptr node; 350 1.1 mrg 351 1.1 mrg if (object == nil) 352 1.1 mrg return OBJC_SYNC_SUCCESS; 353 1.1 mrg 354 1.1 mrg #ifndef SYNC_CACHE_DISABLE 355 1.1 mrg if (lock_cache != NULL) 356 1.1 mrg { 357 1.1 mrg int i; 358 1.1 mrg 359 1.1 mrg /* Find the lock in the cache. */ 360 1.1 mrg node = NULL; 361 1.1 mrg for (i = 0; i < SYNC_CACHE_SIZE; i++) 362 1.1 mrg { 363 1.1 mrg lock_node_ptr locked_node = lock_cache[i]; 364 1.1 mrg 365 1.1 mrg if (locked_node != NULL && locked_node->object == object) 366 1.1 mrg { 367 1.1 mrg node = locked_node; 368 1.1 mrg break; 369 1.1 mrg } 370 1.1 mrg } 371 1.1 mrg /* Note that, if a node was found in the cache, the variable i 372 1.1 mrg now holds the index where it was found, which will be used to 373 1.1 mrg remove it from the cache. */ 374 1.1 mrg if (node != NULL) 375 1.1 mrg { 376 1.1 mrg if (node->recursive_usage_count > 0) 377 1.1 mrg { 378 1.1 mrg node->recursive_usage_count--; 379 1.1 mrg return OBJC_SYNC_SUCCESS; 380 1.1 mrg } 381 1.1 mrg else 382 1.1 mrg { 383 1.1 mrg /* We need to do a real unlock. */ 384 1.1 mrg hash = SYNC_OBJECT_HASH(object); 385 1.1 mrg 386 1.1 mrg /* TODO: If we had atomic increase/decrease operations 387 1.1 mrg with memory barriers, we could avoid the lock 388 1.1 mrg here! */ 389 1.1 mrg objc_mutex_lock (sync_pool_protection_locks[hash]); 390 1.1 mrg node->usage_count--; 391 1.1 mrg /* Normally, we do not reset object to nil here. We'll 392 1.1 mrg leave the lock associated with that object, at zero 393 1.3 mrg usage count. This makes it slightly more efficient to 394 1.1 mrg provide a lock for that object if (as likely) 395 1.1 mrg requested again. If the object is deallocated, we 396 1.1 mrg don't care. It will never match a new lock that is 397 1.1 mrg requested, and the node will be reused at some point. 398 1.1 mrg 399 1.1 mrg But, if garbage collection is enabled, leaving a 400 1.1 mrg pointer to the object in memory might prevent the 401 1.1 mrg object from being released. In that case, we remove 402 1.1 mrg it (TODO: maybe we should avoid using the garbage 403 1.1 mrg collector at all ? Nothing is ever deallocated in 404 1.1 mrg this file). */ 405 1.1 mrg #if OBJC_WITH_GC 406 1.1 mrg node->object = nil; 407 1.1 mrg #endif 408 1.1 mrg objc_mutex_unlock (sync_pool_protection_locks[hash]); 409 1.1 mrg 410 1.1 mrg /* PS: Between objc_mutex_unlock 411 1.1 mrg (sync_pool_protection_locks[hash]) and 412 1.1 mrg objc_mutex_unlock (node->lock), the pool is unlocked 413 1.1 mrg so other threads may allocate this same lock to 414 1.1 mrg another object (!). This is not a problem, but it is 415 1.1 mrg curious. */ 416 1.1 mrg objc_mutex_unlock (node->lock); 417 1.1 mrg 418 1.1 mrg /* Remove the node from the cache. */ 419 1.1 mrg lock_cache[i] = NULL; 420 1.1 mrg 421 1.1 mrg return OBJC_SYNC_SUCCESS; 422 1.1 mrg } 423 1.1 mrg } 424 1.1 mrg } 425 1.1 mrg #endif 426 1.1 mrg 427 1.1 mrg /* The cache either wasn't there, or didn't work (eg, we overflowed 428 1.1 mrg it at some point and stopped recording new locks in the cache). 429 1.1 mrg Proceed with a full search of the lock pool. */ 430 1.1 mrg hash = SYNC_OBJECT_HASH(object); 431 1.1 mrg 432 1.1 mrg objc_mutex_lock (sync_pool_protection_locks[hash]); 433 1.1 mrg 434 1.1 mrg /* Search for an existing lock for 'object'. */ 435 1.1 mrg node = sync_pool_array[hash]; 436 1.1 mrg 437 1.1 mrg while (node != NULL) 438 1.1 mrg { 439 1.1 mrg if (node->object == object) 440 1.1 mrg { 441 1.1 mrg /* We found the lock. */ 442 1.1 mrg node->usage_count--; 443 1.1 mrg objc_mutex_unlock (sync_pool_protection_locks[hash]); 444 1.1 mrg 445 1.1 mrg objc_mutex_unlock (node->lock); 446 1.1 mrg 447 1.1 mrg /* No need to remove the node from the cache, since it 448 1.1 mrg wasn't found in the cache when we looked for it! */ 449 1.1 mrg return OBJC_SYNC_SUCCESS; 450 1.1 mrg } 451 1.1 mrg 452 1.1 mrg node = node->next; 453 1.1 mrg } 454 1.1 mrg 455 1.1 mrg objc_mutex_unlock (sync_pool_protection_locks[hash]); 456 1.1 mrg 457 1.1 mrg /* A lock for 'object' to unlock could not be found (!!). */ 458 1.1 mrg return OBJC_SYNC_NOT_OWNING_THREAD_ERROR; 459 1.1 mrg } 460