Blame sysdeps/unix/sysv/linux/s390/elision-lock.c

Packit 6c4009
/* Elided pthread mutex lock.
Packit 6c4009
   Copyright (C) 2014-2018 Free Software Foundation, Inc.
Packit 6c4009
   This file is part of the GNU C Library.
Packit 6c4009
Packit 6c4009
   The GNU C Library is free software; you can redistribute it and/or
Packit 6c4009
   modify it under the terms of the GNU Lesser General Public
Packit 6c4009
   License as published by the Free Software Foundation; either
Packit 6c4009
   version 2.1 of the License, or (at your option) any later version.
Packit 6c4009
Packit 6c4009
   The GNU C Library is distributed in the hope that it will be useful,
Packit 6c4009
   but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit 6c4009
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Packit 6c4009
   Lesser General Public License for more details.
Packit 6c4009
Packit 6c4009
   You should have received a copy of the GNU Lesser General Public
Packit 6c4009
   License along with the GNU C Library; if not, see
Packit 6c4009
   <http://www.gnu.org/licenses/>.  */
Packit 6c4009
Packit 6c4009
#include <pthread.h>
Packit 6c4009
#include <pthreadP.h>
Packit 6c4009
#include <lowlevellock.h>
Packit 6c4009
#include <htm.h>
Packit 6c4009
#include <elision-conf.h>
Packit 6c4009
#include <stdint.h>
Packit 6c4009
Packit 6c4009
#if !defined(LLL_LOCK) && !defined(EXTRAARG)
Packit 6c4009
/* Make sure the configuration code is always linked in for static
Packit 6c4009
   libraries.  */
Packit 6c4009
#include "elision-conf.c"
Packit 6c4009
#endif
Packit 6c4009
Packit 6c4009
#ifndef EXTRAARG
Packit 6c4009
#define EXTRAARG
Packit 6c4009
#endif
Packit 6c4009
#ifndef LLL_LOCK
Packit 6c4009
#define LLL_LOCK(a,b) lll_lock(a,b), 0
Packit 6c4009
#endif
Packit 6c4009
Packit 6c4009
#define aconf __elision_aconf
Packit 6c4009
Packit 6c4009
/* Adaptive lock using transactions.
Packit 6c4009
   By default the lock region is run as a transaction, and when it
Packit 6c4009
   aborts or the lock is busy the lock adapts itself.  */
Packit 6c4009
Packit 6c4009
int
Packit 6c4009
__lll_lock_elision (int *futex, short *adapt_count, EXTRAARG int private)
Packit 6c4009
{
Packit 6c4009
  /* adapt_count can be accessed concurrently; these accesses can be both
Packit 6c4009
     inside of transactions (if critical sections are nested and the outer
Packit 6c4009
     critical section uses lock elision) and outside of transactions.  Thus,
Packit 6c4009
     we need to use atomic accesses to avoid data races.  However, the
Packit 6c4009
     value of adapt_count is just a hint, so relaxed MO accesses are
Packit 6c4009
     sufficient.  */
Packit 6c4009
    if (atomic_load_relaxed (adapt_count) <= 0 && aconf.try_tbegin > 0)
Packit 6c4009
    {
Packit 6c4009
      /* Start a transaction and retry it automatically if it aborts with
Packit 6c4009
	 _HTM_TBEGIN_TRANSIENT.  This macro calls tbegin at most retry_cnt
Packit 6c4009
	 + 1 times.  The second argument is considered as retry_cnt.  */
Packit 6c4009
      int status = __libc_tbegin_retry ((void *) 0, aconf.try_tbegin - 1);
Packit 6c4009
      if (__glibc_likely (status == _HTM_TBEGIN_STARTED))
Packit 6c4009
	{
Packit 6c4009
	  /* Check the futex to make sure nobody has touched it in the
Packit 6c4009
	     mean time.  This forces the futex into the cache and makes
Packit 6c4009
	     sure the transaction aborts if another thread acquires the lock
Packit 6c4009
	     concurrently.  */
Packit 6c4009
	  if (__glibc_likely (atomic_load_relaxed (futex) == 0))
Packit 6c4009
	    /* Lock was free.  Return to user code in a transaction.  */
Packit 6c4009
	    return 0;
Packit 6c4009
Packit 6c4009
	  /* Lock was busy.  Fall back to normal locking.
Packit 6c4009
	     This can be the case if e.g. adapt_count was decremented to zero
Packit 6c4009
	     by a former release and another thread has been waken up and
Packit 6c4009
	     acquired it.  */
Packit 6c4009
	  if (__glibc_likely (__libc_tx_nesting_depth () <= 1))
Packit 6c4009
	    {
Packit 6c4009
	      /* In a non-nested transaction there is no need to abort,
Packit 6c4009
		 which is expensive.  Simply end the started transaction.  */
Packit 6c4009
	      __libc_tend ();
Packit 6c4009
	      /* Don't try to use transactions for the next couple of times.
Packit 6c4009
		 See above for why relaxed MO is sufficient.  */
Packit 6c4009
	      if (aconf.skip_lock_busy > 0)
Packit 6c4009
		atomic_store_relaxed (adapt_count, aconf.skip_lock_busy);
Packit 6c4009
	    }
Packit 6c4009
	  else /* nesting depth is > 1 */
Packit 6c4009
	    {
Packit 6c4009
	      /* A nested transaction will abort eventually because it
Packit 6c4009
		 cannot make any progress before *futex changes back to 0.
Packit 6c4009
		 So we may as well abort immediately.
Packit 6c4009
		 This persistently aborts the outer transaction to force
Packit 6c4009
		 the outer mutex use the default lock instead of retrying
Packit 6c4009
		 with transactions until the try_tbegin of the outer mutex
Packit 6c4009
		 is zero.
Packit 6c4009
		 The adapt_count of this inner mutex is not changed,
Packit 6c4009
		 because using the default lock with the inner mutex
Packit 6c4009
		 would abort the outer transaction.  */
Packit 6c4009
	      __libc_tabort (_HTM_FIRST_USER_ABORT_CODE | 1);
Packit 6c4009
	      __builtin_unreachable ();
Packit 6c4009
	    }
Packit 6c4009
	}
Packit 6c4009
      else if (status != _HTM_TBEGIN_TRANSIENT)
Packit 6c4009
	{
Packit 6c4009
	  /* A persistent abort (cc 1 or 3) indicates that a retry is
Packit 6c4009
	     probably futile.  Use the normal locking now and for the
Packit 6c4009
	     next couple of calls.
Packit 6c4009
	     Be careful to avoid writing to the lock.  See above for why
Packit 6c4009
	     relaxed MO is sufficient.  */
Packit 6c4009
	  if (aconf.skip_lock_internal_abort > 0)
Packit 6c4009
	    atomic_store_relaxed (adapt_count,
Packit 6c4009
				  aconf.skip_lock_internal_abort);
Packit 6c4009
	}
Packit 6c4009
      else
Packit 6c4009
	{
Packit 6c4009
	  /* The transaction failed for some retries with
Packit 6c4009
	     _HTM_TBEGIN_TRANSIENT.  Use the normal locking now and for the
Packit 6c4009
	     next couple of calls.  */
Packit 6c4009
	  if (aconf.skip_lock_out_of_tbegin_retries > 0)
Packit 6c4009
	    atomic_store_relaxed (adapt_count,
Packit 6c4009
				  aconf.skip_lock_out_of_tbegin_retries);
Packit 6c4009
	}
Packit 6c4009
    }
Packit 6c4009
Packit 6c4009
  /* Use normal locking as fallback path if the transaction does not
Packit 6c4009
     succeed.  */
Packit 6c4009
  return LLL_LOCK ((*futex), private);
Packit 6c4009
}