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